Current section

Files

Jump to
phoenix priv static phoenix.js
Raw

priv/static/phoenix.js

(function(/*! Brunch !*/) {
'use strict';
var globals = typeof window !== 'undefined' ? window : global;
if (typeof globals.require === 'function') return;
var modules = {};
var cache = {};
var has = function(object, name) {
return ({}).hasOwnProperty.call(object, name);
};
var expand = function(root, name) {
var results = [], parts, part;
if (/^\.\.?(\/|$)/.test(name)) {
parts = [root, name].join('/').split('/');
} else {
parts = name.split('/');
}
for (var i = 0, length = parts.length; i < length; i++) {
part = parts[i];
if (part === '..') {
results.pop();
} else if (part !== '.' && part !== '') {
results.push(part);
}
}
return results.join('/');
};
var dirname = function(path) {
return path.split('/').slice(0, -1).join('/');
};
var localRequire = function(path) {
return function(name) {
var dir = dirname(path);
var absolute = expand(dir, name);
return globals.require(absolute, path);
};
};
var initModule = function(name, definition) {
var module = {id: name, exports: {}};
cache[name] = module;
definition(module.exports, localRequire(name), module);
return module.exports;
};
var require = function(name, loaderPath) {
var path = expand(name, '.');
if (loaderPath == null) loaderPath = '/';
if (has(cache, path)) return cache[path].exports;
if (has(modules, path)) return initModule(path, modules[path]);
var dirIndex = expand(path, './index');
if (has(cache, dirIndex)) return cache[dirIndex].exports;
if (has(modules, dirIndex)) return initModule(dirIndex, modules[dirIndex]);
throw new Error('Cannot find module "' + name + '" from '+ '"' + loaderPath + '"');
};
var define = function(bundle, fn) {
if (typeof bundle === 'object') {
for (var key in bundle) {
if (has(bundle, key)) {
modules[key] = bundle[key];
}
}
} else {
modules[bundle] = fn;
}
};
var list = function() {
var result = [];
for (var item in modules) {
if (has(modules, item)) {
result.push(item);
}
}
return result;
};
globals.require = require;
globals.require.define = define;
globals.require.register = define;
globals.require.list = list;
globals.require.brunch = true;
})();
require.define({'phoenix': function(exports, require, module){ "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"); } };
// Phoenix Channels JavaScript client
//
// ## Socket Connection
//
// A single connection is established to the server and
// channels are mulitplexed over the connection.
// Connect to the server using the `Socket` class:
//
// let socket = new Socket("/ws")
// socket.connect()
//
// The `Socket` constructor takes the mount point of the socket
// as well as options that can be found in the Socket docs,
// such as configuring the `LongPoller` transport, and heartbeat.
//
//
// ## Channels
//
// Channels are isolated, concurrent processes on the server that
// subscribe to topics and broker events between the client and server.
// To join a channel, you must provide the topic, and channel params for
// authorization. Here's an example chat room example where `"new_msg"`
// events are listened for, messages are pushed to the server, and
// the channel is joined with ok/error matches, and `after` hook:
//
// let chan = socket.chan("rooms:123", {token: roomToken})
// chan.on("new_msg", msg => console.log("Got message", msg) )
// $input.onEnter( e => {
// chan.push("new_msg", {body: e.target.val})
// .receive("ok", (message) => console.log("created message", message) )
// .receive("error", (reasons) => console.log("create failed", reasons) )
// .after(10000, () => console.log("Networking issue. Still waiting...") )
// })
// chan.join()
// .receive("ok", ({messages}) => console.log("catching up", messages) )
// .receive("error", ({reason}) => console.log("failed join", reason) )
// .after(10000, () => console.log("Networking issue. Still waiting...") )
//
//
// ## Joining
//
// Joining a channel with `chan.join(topic, params)`, binds the params to
// `chan.params`. Subsequent rejoins will send up the modified params for
// updating authorization params, or passing up last_message_id information.
// Successful joins receive an "ok" status, while unsuccessful joins
// receive "error".
//
//
// ## Pushing Messages
//
// From the prevoius example, we can see that pushing messages to the server
// can be done with `chan.push(eventName, payload)` and we can optionally
// receive responses from the push. Additionally, we can use
// `after(millsec, callback)` to abort waiting for our `receive` hooks and
// take action after some period of waiting.
//
//
// ## Socket Hooks
//
// Lifecycle events of the multiplexed connection can be hooked into via
// `socket.onError()` and `socket.onClose()` events, ie:
//
// socket.onError( () => console.log("there was an error with the connection!") )
// socket.onClose( () => console.log("the connection dropped") )
//
//
// ## Channel Hooks
//
// For each joined channel, you can bind to `onError` and `onClose` events
// to monitor the channel lifecycle, ie:
//
// chan.onError( () => console.log("there was an error!") )
// chan.onClose( () => console.log("the channel has gone away gracefully") )
//
// ### onError hooks
//
// `onError` hooks are invoked if the socket connection drops, or the channel
// crashes on the server. In either case, a channel rejoin is attemtped
// automatically in an exponential backoff manner.
//
// ### onClose hooks
//
// `onClose` hooks are invoked only in two cases. 1) the channel explicitly
// closed on the server, or 2). The client explicitly closed, by calling
// `chan.leave()`
//
var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
var CHAN_STATES = {
closed: "closed",
errored: "errored",
joined: "joined",
joining: "joining" };
var CHAN_EVENTS = {
close: "phx_close",
error: "phx_error",
join: "phx_join",
reply: "phx_reply",
leave: "phx_leave"
};
var Push = (function () {
// Initializes the Push
//
// chan - The Channel
// event - The event, ie `"phx_join"`
// payload - The payload, ie `{user_id: 123}`
//
function Push(chan, event, payload) {
_classCallCheck(this, Push);
this.chan = chan;
this.event = event;
this.payload = payload || {};
this.receivedResp = null;
this.afterHook = null;
this.recHooks = [];
this.sent = false;
}
_prototypeProperties(Push, null, {
send: {
value: function send() {
var _this = this;
var ref = this.chan.socket.makeRef();
this.refEvent = this.chan.replyEventName(ref);
this.receivedResp = null;
this.sent = false;
this.chan.on(this.refEvent, function (payload) {
_this.receivedResp = payload;
_this.matchReceive(payload);
_this.cancelRefEvent();
_this.cancelAfter();
});
this.startAfter();
this.sent = true;
this.chan.socket.push({
topic: this.chan.topic,
event: this.event,
payload: this.payload,
ref: ref
});
},
writable: true,
configurable: true
},
receive: {
value: function receive(status, callback) {
if (this.receivedResp && this.receivedResp.status === status) {
callback(this.receivedResp.response);
}
this.recHooks.push({ status: status, callback: callback });
return this;
},
writable: true,
configurable: true
},
after: {
value: function after(ms, callback) {
if (this.afterHook) {
throw "only a single after hook can be applied to a push";
}
var timer = null;
if (this.sent) {
timer = setTimeout(callback, ms);
}
this.afterHook = { ms: ms, callback: callback, timer: timer };
return this;
},
writable: true,
configurable: true
},
matchReceive: {
// private
value: function matchReceive(_ref) {
var status = _ref.status;
var response = _ref.response;
var ref = _ref.ref;
this.recHooks.filter(function (h) {
return h.status === status;
}).forEach(function (h) {
return h.callback(response);
});
},
writable: true,
configurable: true
},
cancelRefEvent: {
value: function cancelRefEvent() {
this.chan.off(this.refEvent);
},
writable: true,
configurable: true
},
cancelAfter: {
value: function cancelAfter() {
if (!this.afterHook) {
return;
}
clearTimeout(this.afterHook.timer);
this.afterHook.timer = null;
},
writable: true,
configurable: true
},
startAfter: {
value: function startAfter() {
var _this = this;
if (!this.afterHook) {
return;
}
var callback = function () {
_this.cancelRefEvent();
_this.afterHook.callback();
};
this.afterHook.timer = setTimeout(callback, this.afterHook.ms);
},
writable: true,
configurable: true
}
});
return Push;
})();
var Channel = exports.Channel = (function () {
function Channel(topic, params, socket) {
var _this = this;
_classCallCheck(this, Channel);
this.state = CHAN_STATES.closed;
this.topic = topic;
this.params = params || {};
this.socket = socket;
this.bindings = [];
this.joinedOnce = false;
this.joinPush = new Push(this, CHAN_EVENTS.join, this.params);
this.pushBuffer = [];
this.joinPush.receive("ok", function () {
_this.state = CHAN_STATES.joined;
});
this.onClose(function () {
_this.state = CHAN_STATES.closed;
_this.socket.remove(_this);
});
this.onError(function (reason) {
_this.state = CHAN_STATES.errored;
setTimeout(function () {
return _this.rejoinUntilConnected();
}, _this.socket.reconnectAfterMs);
});
this.on(CHAN_EVENTS.reply, function (payload) {
_this.trigger(_this.replyEventName(payload.ref), payload);
});
}
_prototypeProperties(Channel, null, {
rejoinUntilConnected: {
value: function rejoinUntilConnected() {
var _this = this;
if (this.state !== CHAN_STATES.errored) {
return;
}
if (this.socket.isConnected()) {
this.rejoin();
} else {
setTimeout(function () {
return _this.rejoinUntilConnected();
}, this.socket.reconnectAfterMs);
}
},
writable: true,
configurable: true
},
join: {
value: function join() {
if (this.joinedOnce) {
throw "tried to join mulitple times. 'join' can only be called a singe time per channel instance";
} else {
this.joinedOnce = true;
}
this.sendJoin();
return this.joinPush;
},
writable: true,
configurable: true
},
onClose: {
value: function onClose(callback) {
this.on(CHAN_EVENTS.close, callback);
},
writable: true,
configurable: true
},
onError: {
value: function onError(callback) {
this.on(CHAN_EVENTS.error, function (reason) {
return callback(reason);
});
},
writable: true,
configurable: true
},
on: {
value: function on(event, callback) {
this.bindings.push({ event: event, callback: callback });
},
writable: true,
configurable: true
},
off: {
value: function off(event) {
this.bindings = this.bindings.filter(function (bind) {
return bind.event !== event;
});
},
writable: true,
configurable: true
},
canPush: {
value: function canPush() {
return this.socket.isConnected() && this.state === CHAN_STATES.joined;
},
writable: true,
configurable: true
},
push: {
value: function push(event, payload) {
if (!this.joinedOnce) {
throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use chan.join() before pushing events";
}
var pushEvent = new Push(this, event, payload);
if (this.canPush()) {
pushEvent.send();
} else {
this.pushBuffer.push(pushEvent);
}
return pushEvent;
},
writable: true,
configurable: true
},
leave: {
// Leaves the channel
//
// Unsubscribes from server events, and
// instructs channel to terminate on server
//
// Triggers onClose() hooks
//
// To receive leave acknowledgements, use the a `receive`
// hook to bind to the server ack, ie:
//
// chan.leave().receive("ok", () => alert("left!") )
//
value: function leave() {
var _this = this;
return this.push(CHAN_EVENTS.leave).receive("ok", function () {
_this.trigger(CHAN_EVENTS.close, "leave");
});
},
writable: true,
configurable: true
},
isMember: {
// private
value: function isMember(topic) {
return this.topic === topic;
},
writable: true,
configurable: true
},
sendJoin: {
value: function sendJoin() {
this.state = CHAN_STATES.joining;
this.joinPush.send();
},
writable: true,
configurable: true
},
rejoin: {
value: function rejoin() {
this.sendJoin();
this.pushBuffer.forEach(function (pushEvent) {
return pushEvent.send();
});
this.pushBuffer = [];
},
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
},
replyEventName: {
value: function replyEventName(ref) {
return "chan_reply_" + ref;
},
writable: true,
configurable: true
}
});
return Channel;
})();
var Socket = 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 millisec interval to send a heartbeat message
// reconnectAfterMs - The millisec interval to reconnect after connection loss
// logger - The optional function for specialized logging, ie:
// `logger: function(msg){ console.log(msg) }`
// longpoller_timeout - The maximum timeout of a long poll AJAX request.
// Defaults to 20s (double the server long poll timer).
//
// For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
//
function Socket(endPoint) {
var opts = arguments[1] === undefined ? {} : arguments[1];
_classCallCheck(this, Socket);
this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
this.reconnectTimer = null;
this.channels = [];
this.sendBuffer = [];
this.ref = 0;
this.transport = opts.transport || window.WebSocket || LongPoller;
this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
this.reconnectAfterMs = opts.reconnectAfterMs || 5000;
this.logger = opts.logger || function () {}; // noop
this.longpoller_timeout = opts.longpoller_timeout || 20000;
this.endPoint = this.expandEndpoint(endPoint);
}
_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
},
disconnect: {
value: function disconnect(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
},
connect: {
value: function connect() {
var _this = this;
this.disconnect(function () {
_this.conn = new _this.transport(_this.endPoint);
_this.conn.timeout = _this.longpoller_timeout;
_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
},
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(function(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;
this.flushSendBuffer();
clearInterval(this.reconnectTimer);
if (!this.conn.skipHeartbeat) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = setInterval(function () {
return _this.sendHeartbeat();
}, this.heartbeatIntervalMs);
}
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);
this.triggerChanError();
clearInterval(this.reconnectTimer);
clearInterval(this.heartbeatTimer);
this.reconnectTimer = setInterval(function () {
return _this.connect();
}, 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.triggerChanError();
this.stateChangeCallbacks.error.forEach(function (callback) {
return callback(error);
});
},
writable: true,
configurable: true
},
triggerChanError: {
value: function triggerChanError() {
this.channels.forEach(function (chan) {
return chan.trigger(CHAN_EVENTS.error);
});
},
writable: true,
configurable: true
},
connectionState: {
value: function 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";
}
},
writable: true,
configurable: true
},
isConnected: {
value: function isConnected() {
return this.connectionState() === "open";
},
writable: true,
configurable: true
},
remove: {
value: function remove(chan) {
this.channels = this.channels.filter(function (c) {
return !c.isMember(chan.topic);
});
},
writable: true,
configurable: true
},
chan: {
value: function chan(topic, params) {
var chan = new Channel(topic, params, this);
this.channels.push(chan);
return chan;
},
writable: true,
configurable: true
},
push: {
value: function push(data) {
var _this = this;
var callback = function () {
return _this.conn.send(JSON.stringify(data));
};
if (this.isConnected()) {
callback();
} else {
this.sendBuffer.push(callback);
}
},
writable: true,
configurable: true
},
makeRef: {
// Return the next message ref, accounting for overflows
value: function makeRef() {
var newRef = this.ref + 1;
if (newRef === this.ref) {
this.ref = 0;
} else {
this.ref = newRef;
}
return this.ref.toString();
},
writable: true,
configurable: true
},
sendHeartbeat: {
value: function sendHeartbeat() {
this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef() });
},
writable: true,
configurable: true
},
flushSendBuffer: {
value: function flushSendBuffer() {
if (this.isConnected() && this.sendBuffer.length > 0) {
this.sendBuffer.forEach(function (callback) {
return callback();
});
this.sendBuffer = [];
}
},
writable: true,
configurable: true
},
onConnMessage: {
value: function onConnMessage(rawMessage) {
this.log("message received:");
this.log(rawMessage);
var _JSON$parse = JSON.parse(rawMessage.data);
var topic = _JSON$parse.topic;
var event = _JSON$parse.event;
var payload = _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;
})();
var LongPoller = 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.upgradeEndpoint = this.normalizeEndpoint(endPoint);
this.pollEndpoint = this.upgradeEndpoint + (/\/$/.test(endPoint) ? "poll" : "/poll");
this.readyState = SOCKET_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 = SOCKET_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 === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
return;
}
Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
if (resp) {
var status = resp.status;
var token = resp.token;
var sig = resp.sig;
var messages = resp.messages;
_this.token = token;
_this.sig = sig;
} else {
var status = 0;
}
switch (status) {
case 200:
messages.forEach(function (msg) {
return _this.onmessage({ data: JSON.stringify(msg) });
});
_this.poll();
break;
case 204:
_this.poll();
break;
case 410:
_this.readyState = SOCKET_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;
Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
if (!resp || resp.status !== 200) {
_this.onerror(status);
_this.closeAndRetry();
}
});
},
writable: true,
configurable: true
},
close: {
value: function close(code, reason) {
this.readyState = SOCKET_STATES.closed;
this.onclose();
},
writable: true,
configurable: true
}
});
return LongPoller;
})();
var Ajax = exports.Ajax = (function () {
function Ajax() {
_classCallCheck(this, Ajax);
}
_prototypeProperties(Ajax, {
request: {
value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
if (window.XDomainRequest) {
var req = new XDomainRequest(); // IE8, IE9
this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
} else {
var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
}
},
writable: true,
configurable: true
},
xdomainRequest: {
value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
var _this = this;
req.timeout = timeout;
req.open(method, endPoint);
req.onload = function () {
var response = _this.parseJSON(req.responseText);
callback && callback(response);
};
if (ontimeout) {
req.ontimeout = ontimeout;
}
// Work around bug in IE9 that requires an attached onprogress handler
req.onprogress = function () {};
req.send(body);
},
writable: true,
configurable: true
},
xhrRequest: {
value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
var _this = this;
req.timeout = timeout;
req.open(method, endPoint, true);
req.setRequestHeader("Content-Type", accept);
req.onerror = function () {
callback && callback(null);
};
req.onreadystatechange = function () {
if (req.readyState === _this.states.complete && callback) {
var response = _this.parseJSON(req.responseText);
callback(response);
}
};
if (ontimeout) {
req.ontimeout = ontimeout;
}
req.send(body);
},
writable: true,
configurable: true
},
parseJSON: {
value: function parseJSON(resp) {
return resp && resp !== "" ? JSON.parse(resp) : null;
},
writable: true,
configurable: true
}
});
return Ajax;
})();
Ajax.states = { complete: 4 };
Object.defineProperty(exports, "__esModule", {
value: true
});
}});
if(typeof(window) === 'object' && !window.Phoenix){ window.Phoenix = require('phoenix') };