Comparing version 1.2.1 to 1.3.0
# Changelog | ||
## 1.3.0 (2017-07-28) | ||
See these [`1.2.x` to `1.3.x` upgrade instructions](https://gist.github.com/chrismccord/71ab10d433c98b714b75c886eff17357) to bring your existing apps up to speed. | ||
* Enhancements | ||
* [Generator] Add new `phx.new`, `phx.new.web`, `phx.new.ecto` project generators with improved application structure and support for umbrella applications | ||
* [Generator] Add new `phx.gen.html` and `phx.gen.json` resource generators with improved isolation of API boundaries | ||
* [Controller] Add `current_path` and `current_url` to generate a connection's path and url | ||
* [Controller] Introduce `action_fallback` to registers a plug to call as a fallback to the controller action | ||
* [Controller] Wrap exceptions at controller to maintain connection state | ||
* [Channel] Add ability to configure channel event logging with `:log_join` and `:log_handle_in` options | ||
* [Channel] Warn on unhandled `handle_info/2` messages | ||
* [Channel] Channels now distinguish from graceful exits and application restarts, allowing clients to enter error mode and reconnected after cold deploys. | ||
* [Channel] Add V2 of wire channel wire protocol with resolved race conditions and compacted payloads | ||
* [ChannelTest] Subscribe `connect` to `UserSocket.id` to support testing forceful disconnects | ||
* [Socket] Support static `:assigns` when defining channel routes | ||
* [Router] Document `match` support for matching on any HTTP method with the special `:*` argument | ||
* [Router] Populate `conn.path_params` with path parameters for the route | ||
* [ConnTest] Add `redirected_params/1` to return the named params matched in the router for the redirected URL | ||
* [Digester] Add `mix phx.digest.clean` to remove old versions of compiled assets | ||
* [phx.new] Add Erlang 20 support in `phx.new` installer archive | ||
* [phx.new] Use new `lib/my_app` and `lib/my_app_web` directory structure | ||
* [phx.new] Use new `MyAppWeb` alias convention for web modules | ||
* Bug Fixes | ||
* [Controller] Harden local redirect against arbitrary URL redirection | ||
* [Controller] Fix issue causing flash session to remain when using `clear_flash/1` | ||
* Deprecations | ||
* [Generator] All `phoenix.*` mix tasks have been deprecated in favor of new `phx.*` tasks | ||
* JavaScript client enhancements | ||
* Use V2 channel wire protocol support | ||
* Add ability to pass `encode` and `decode` functions to socket constructor for custom encoding and decoding of outgoing and incoming messages. | ||
* Detect heartbeat timeouts on client to handle ungraceful connection loss for faster socket error detection | ||
* Add support for AMD/RequireJS | ||
* JavaScript client bug fixes | ||
* Resolve race conditions when join timeouts occur on client, while server channel successfully joins | ||
## 1.2.2 (2017-3-14) | ||
* Big Fixes | ||
* [Controller] Harden local redirect against arbitrary URL redirection | ||
## 1.2.1 (2016-8-11) | ||
@@ -4,0 +49,0 @@ |
{ | ||
"name": "phoenix", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "The official JavaScript client for the Phoenix web framework.", | ||
@@ -13,11 +13,25 @@ "license": "MIT", | ||
"devDependencies": { | ||
"babel-brunch": "~6.0.0", | ||
"brunch": "~2.6.5", | ||
"documentation": "^4.0.0-rc.1", | ||
"jsdom": "9.8.3", | ||
"jsdom-global": "2.1.0", | ||
"mocha": "~2.4.4", | ||
"babel-brunch": "~6.0.0", | ||
"mock-socket": "^6.0.1", | ||
"sinon": "^1.17.6", | ||
"uglify-js-brunch": "~2.0.1" | ||
}, | ||
"files": ["README.md", "LICENSE.md", "package.json", "priv/static/phoenix.js", "web/static/js/phoenix.js"], | ||
"files": [ | ||
"README.md", | ||
"LICENSE.md", | ||
"package.json", | ||
"priv/static/phoenix.js", | ||
"assets/js/phoenix.js" | ||
], | ||
"scripts": { | ||
"test": "./node_modules/.bin/mocha ./web/test/**/*.js --compilers js:babel-register" | ||
"test": "./node_modules/.bin/mocha ./assets/test/**/*.js --compilers js:babel-register -r jsdom-global/register", | ||
"docs": "documentation build assets/js/phoenix.js -f html -o doc/js", | ||
"watch": "brunch watch", | ||
"build": "brunch build" | ||
} | ||
} |
@@ -1,8 +0,8 @@ | ||
(function(exports){ | ||
(function (global, factory) { | ||
typeof exports === 'object' ? factory(exports) : | ||
typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
factory(global.Phoenix = global.Phoenix || {}); | ||
}(this, (function (exports) { | ||
"use strict"; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -12,2 +12,8 @@ value: true | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
@@ -17,173 +23,187 @@ | ||
// Phoenix Channels JavaScript client | ||
// | ||
// ## Socket Connection | ||
// | ||
// A single connection is established to the server and | ||
// channels are multiplexed over the connection. | ||
// Connect to the server using the `Socket` class: | ||
// | ||
// let socket = new Socket("/ws", {params: {userToken: "123"}}) | ||
// socket.connect() | ||
// | ||
// The `Socket` constructor takes the mount point of the socket, | ||
// the authentication params, as well as options that can be found in | ||
// the Socket docs, such as configuring the `LongPoll` 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/timeout matches: | ||
// | ||
// let channel = socket.channel("room:123", {token: roomToken}) | ||
// channel.on("new_msg", msg => console.log("Got message", msg) ) | ||
// $input.onEnter( e => { | ||
// channel.push("new_msg", {body: e.target.val}, 10000) | ||
// .receive("ok", (msg) => console.log("created message", msg) ) | ||
// .receive("error", (reasons) => console.log("create failed", reasons) ) | ||
// .receive("timeout", () => console.log("Networking issue...") ) | ||
// }) | ||
// channel.join() | ||
// .receive("ok", ({messages}) => console.log("catching up", messages) ) | ||
// .receive("error", ({reason}) => console.log("failed join", reason) ) | ||
// .receive("timeout", () => console.log("Networking issue. Still waiting...") ) | ||
// | ||
// | ||
// ## Joining | ||
// | ||
// Creating a channel with `socket.channel(topic, params)`, binds the params to | ||
// `channel.params`, which are sent up on `channel.join()`. | ||
// 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". | ||
// | ||
// ## Duplicate Join Subscriptions | ||
// | ||
// While the client may join any number of topics on any number of channels, | ||
// the client may only hold a single subscription for each unique topic at any | ||
// given time. When attempting to create a duplicate subscription, | ||
// the server will close the existing channel, log a warning, and | ||
// spawn a new channel for the topic. The client will have their | ||
// `channel.onClose` callbacks fired for the existing channel, and the new | ||
// channel join will have its receive hooks processed as normal. | ||
// | ||
// ## Pushing Messages | ||
// | ||
// From the previous example, we can see that pushing messages to the server | ||
// can be done with `channel.push(eventName, payload)` and we can optionally | ||
// receive responses from the push. Additionally, we can use | ||
// `receive("timeout", callback)` to abort waiting for our other `receive` hooks | ||
// and take action after some period of waiting. The default timeout is 5000ms. | ||
// | ||
// | ||
// ## 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: | ||
// | ||
// channel.onError( () => console.log("there was an error!") ) | ||
// channel.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 attempted | ||
// 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 | ||
// `channel.leave()` | ||
// | ||
// | ||
// ## Presence | ||
// | ||
// The `Presence` object provides features for syncing presence information | ||
// from the server with the client and handling presences joining and leaving. | ||
// | ||
// ### Syncing initial state from the server | ||
// | ||
// `Presence.syncState` is used to sync the list of presences on the server | ||
// with the client's state. An optional `onJoin` and `onLeave` callback can | ||
// be provided to react to changes in the client's local presences across | ||
// disconnects and reconnects with the server. | ||
// | ||
// `Presence.syncDiff` is used to sync a diff of presence join and leave | ||
// events from the server, as they happen. Like `syncState`, `syncDiff` | ||
// accepts optional `onJoin` and `onLeave` callbacks to react to a user | ||
// joining or leaving from a device. | ||
// | ||
// ### Listing Presences | ||
// | ||
// `Presence.list` is used to return a list of presence information | ||
// based on the local state of metadata. By default, all presence | ||
// metadata is returned, but a `listBy` function can be supplied to | ||
// allow the client to select which metadata to use for a given presence. | ||
// For example, you may have a user online from different devices with a | ||
// a metadata status of "online", but they have set themselves to "away" | ||
// on another device. In this case, they app may choose to use the "away" | ||
// status for what appears on the UI. The example below defines a `listBy` | ||
// function which prioritizes the first metadata which was registered for | ||
// each user. This could be the first tab they opened, or the first device | ||
// they came online from: | ||
// | ||
// let state = {} | ||
// state = Presence.syncState(state, stateFromServer) | ||
// let listBy = (id, {metas: [first, ...rest]}) => { | ||
// first.count = rest.length + 1 // count of this user's presences | ||
// first.id = id | ||
// return first | ||
// } | ||
// let onlineUsers = Presence.list(state, listBy) | ||
// | ||
// | ||
// ### Example Usage | ||
// | ||
// // detect if user has joined for the 1st time or from another tab/device | ||
// let onJoin = (id, current, newPres) => { | ||
// if(!current){ | ||
// console.log("user has entered for the first time", newPres) | ||
// } else { | ||
// console.log("user additional presence", newPres) | ||
// } | ||
// } | ||
// // detect if user has left from all tabs/devices, or is still present | ||
// let onLeave = (id, current, leftPres) => { | ||
// if(current.metas.length === 0){ | ||
// console.log("user has left from all devices", leftPres) | ||
// } else { | ||
// console.log("user left from a device", leftPres) | ||
// } | ||
// } | ||
// let presences = {} // client's initial empty presence state | ||
// // receive initial presence data from server, sent after join | ||
// myChannel.on("presence_state", state => { | ||
// presences = Presence.syncState(presences, state, onJoin, onLeave) | ||
// displayUsers(Presence.list(presences)) | ||
// }) | ||
// // receive "presence_diff" from server, containing join/leave events | ||
// myChannel.on("presence_diff", diff => { | ||
// presences = Presence.syncDiff(presences, diff, onJoin, onLeave) | ||
// this.setState({users: Presence.list(room.presences, listBy)}) | ||
// }) | ||
// | ||
var VSN = "1.0.0"; | ||
/** | ||
* Phoenix Channels JavaScript client | ||
* | ||
* ## Socket Connection | ||
* | ||
* A single connection is established to the server and | ||
* channels are multiplexed over the connection. | ||
* Connect to the server using the `Socket` class: | ||
* | ||
* ```javascript | ||
* let socket = new Socket("/socket", {params: {userToken: "123"}}) | ||
* socket.connect() | ||
* ``` | ||
* | ||
* The `Socket` constructor takes the mount point of the socket, | ||
* the authentication params, as well as options that can be found in | ||
* the Socket docs, such as configuring the `LongPoll` 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/timeout matches: | ||
* | ||
* ```javascript | ||
* let channel = socket.channel("room:123", {token: roomToken}) | ||
* channel.on("new_msg", msg => console.log("Got message", msg) ) | ||
* $input.onEnter( e => { | ||
* channel.push("new_msg", {body: e.target.val}, 10000) | ||
* .receive("ok", (msg) => console.log("created message", msg) ) | ||
* .receive("error", (reasons) => console.log("create failed", reasons) ) | ||
* .receive("timeout", () => console.log("Networking issue...") ) | ||
* }) | ||
* channel.join() | ||
* .receive("ok", ({messages}) => console.log("catching up", messages) ) | ||
* .receive("error", ({reason}) => console.log("failed join", reason) ) | ||
* .receive("timeout", () => console.log("Networking issue. Still waiting...") ) | ||
*``` | ||
* | ||
* ## Joining | ||
* | ||
* Creating a channel with `socket.channel(topic, params)`, binds the params to | ||
* `channel.params`, which are sent up on `channel.join()`. | ||
* 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". | ||
* | ||
* ## Duplicate Join Subscriptions | ||
* | ||
* While the client may join any number of topics on any number of channels, | ||
* the client may only hold a single subscription for each unique topic at any | ||
* given time. When attempting to create a duplicate subscription, | ||
* the server will close the existing channel, log a warning, and | ||
* spawn a new channel for the topic. The client will have their | ||
* `channel.onClose` callbacks fired for the existing channel, and the new | ||
* channel join will have its receive hooks processed as normal. | ||
* | ||
* ## Pushing Messages | ||
* | ||
* From the previous example, we can see that pushing messages to the server | ||
* can be done with `channel.push(eventName, payload)` and we can optionally | ||
* receive responses from the push. Additionally, we can use | ||
* `receive("timeout", callback)` to abort waiting for our other `receive` hooks | ||
* and take action after some period of waiting. The default timeout is 5000ms. | ||
* | ||
* | ||
* ## Socket Hooks | ||
* | ||
* Lifecycle events of the multiplexed connection can be hooked into via | ||
* `socket.onError()` and `socket.onClose()` events, ie: | ||
* | ||
* ```javascript | ||
* 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: | ||
* | ||
* ```javascript | ||
* channel.onError( () => console.log("there was an error!") ) | ||
* channel.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 attempted | ||
* 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 | ||
* `channel.leave()` | ||
* | ||
* | ||
* ## Presence | ||
* | ||
* The `Presence` object provides features for syncing presence information | ||
* from the server with the client and handling presences joining and leaving. | ||
* | ||
* ### Syncing initial state from the server | ||
* | ||
* `Presence.syncState` is used to sync the list of presences on the server | ||
* with the client's state. An optional `onJoin` and `onLeave` callback can | ||
* be provided to react to changes in the client's local presences across | ||
* disconnects and reconnects with the server. | ||
* | ||
* `Presence.syncDiff` is used to sync a diff of presence join and leave | ||
* events from the server, as they happen. Like `syncState`, `syncDiff` | ||
* accepts optional `onJoin` and `onLeave` callbacks to react to a user | ||
* joining or leaving from a device. | ||
* | ||
* ### Listing Presences | ||
* | ||
* `Presence.list` is used to return a list of presence information | ||
* based on the local state of metadata. By default, all presence | ||
* metadata is returned, but a `listBy` function can be supplied to | ||
* allow the client to select which metadata to use for a given presence. | ||
* For example, you may have a user online from different devices with | ||
* a metadata status of "online", but they have set themselves to "away" | ||
* on another device. In this case, the app may choose to use the "away" | ||
* status for what appears on the UI. The example below defines a `listBy` | ||
* function which prioritizes the first metadata which was registered for | ||
* each user. This could be the first tab they opened, or the first device | ||
* they came online from: | ||
* | ||
* ```javascript | ||
* let state = {} | ||
* state = Presence.syncState(state, stateFromServer) | ||
* let listBy = (id, {metas: [first, ...rest]}) => { | ||
* first.count = rest.length + 1 // count of this user's presences | ||
* first.id = id | ||
* return first | ||
* } | ||
* let onlineUsers = Presence.list(state, listBy) | ||
* ``` | ||
* | ||
* | ||
* ### Example Usage | ||
*```javascript | ||
* // detect if user has joined for the 1st time or from another tab/device | ||
* let onJoin = (id, current, newPres) => { | ||
* if(!current){ | ||
* console.log("user has entered for the first time", newPres) | ||
* } else { | ||
* console.log("user additional presence", newPres) | ||
* } | ||
* } | ||
* // detect if user has left from all tabs/devices, or is still present | ||
* let onLeave = (id, current, leftPres) => { | ||
* if(current.metas.length === 0){ | ||
* console.log("user has left from all devices", leftPres) | ||
* } else { | ||
* console.log("user left from a device", leftPres) | ||
* } | ||
* } | ||
* let presences = {} // client's initial empty presence state | ||
* // receive initial presence data from server, sent after join | ||
* myChannel.on("presence_state", state => { | ||
* presences = Presence.syncState(presences, state, onJoin, onLeave) | ||
* displayUsers(Presence.list(presences)) | ||
* }) | ||
* // receive "presence_diff" from server, containing join/leave events | ||
* myChannel.on("presence_diff", diff => { | ||
* presences = Presence.syncDiff(presences, diff, onJoin, onLeave) | ||
* this.setState({users: Presence.list(room.presences, listBy)}) | ||
* }) | ||
* ``` | ||
* @module phoenix | ||
*/ | ||
var VSN = "2.0.0"; | ||
var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; | ||
var DEFAULT_TIMEOUT = 10000; | ||
var WS_CLOSE_NORMAL = 1000; | ||
var CHANNEL_STATES = { | ||
@@ -203,2 +223,3 @@ closed: "closed", | ||
}; | ||
var CHANNEL_LIFECYCLE_EVENTS = [CHANNEL_EVENTS.close, CHANNEL_EVENTS.error, CHANNEL_EVENTS.join, CHANNEL_EVENTS.reply, CHANNEL_EVENTS.leave]; | ||
var TRANSPORTS = { | ||
@@ -209,12 +230,11 @@ longpoll: "longpoll", | ||
/** | ||
* Initializes the Push | ||
* @param {Channel} channel - The Channel | ||
* @param {string} event - The event, for example `"phx_join"` | ||
* @param {Object} payload - The payload, for example `{user_id: 123}` | ||
* @param {number} timeout - The push timeout in milliseconds | ||
*/ | ||
var Push = function () { | ||
// Initializes the Push | ||
// | ||
// channel - The Channel | ||
// event - The event, for example `"phx_join"` | ||
// payload - The payload, for example `{user_id: 123}` | ||
// timeout - The push timeout in milliseconds | ||
// | ||
function Push(channel, event, payload, timeout) { | ||
@@ -233,2 +253,8 @@ _classCallCheck(this, Push); | ||
/** | ||
* | ||
* @param {number} timeout | ||
*/ | ||
_createClass(Push, [{ | ||
@@ -238,9 +264,10 @@ key: "resend", | ||
this.timeout = timeout; | ||
this.cancelRefEvent(); | ||
this.ref = null; | ||
this.refEvent = null; | ||
this.receivedResp = null; | ||
this.sent = false; | ||
this.reset(); | ||
this.send(); | ||
} | ||
/** | ||
* | ||
*/ | ||
}, { | ||
@@ -258,5 +285,13 @@ key: "send", | ||
payload: this.payload, | ||
ref: this.ref | ||
ref: this.ref, | ||
join_ref: this.channel.joinRef() | ||
}); | ||
} | ||
/** | ||
* | ||
* @param {*} status | ||
* @param {*} callback | ||
*/ | ||
}, { | ||
@@ -276,7 +311,16 @@ key: "receive", | ||
}, { | ||
key: "reset", | ||
value: function reset() { | ||
this.cancelRefEvent(); | ||
this.ref = null; | ||
this.refEvent = null; | ||
this.receivedResp = null; | ||
this.sent = false; | ||
} | ||
}, { | ||
key: "matchReceive", | ||
value: function matchReceive(_ref) { | ||
var status = _ref.status; | ||
var response = _ref.response; | ||
var ref = _ref.ref; | ||
var status = _ref.status, | ||
response = _ref.response, | ||
ref = _ref.ref; | ||
@@ -309,3 +353,3 @@ this.recHooks.filter(function (h) { | ||
if (this.timeoutTimer) { | ||
return; | ||
this.cancelTimeout(); | ||
} | ||
@@ -341,2 +385,10 @@ this.ref = this.channel.socket.makeRef(); | ||
/** | ||
* | ||
* @param {string} topic | ||
* @param {Object} params | ||
* @param {Socket} socket | ||
*/ | ||
var Channel = exports.Channel = function () { | ||
@@ -386,4 +438,7 @@ function Channel(topic, params, socket) { | ||
} | ||
_this2.socket.log("channel", "timeout " + _this2.topic, _this2.joinPush.timeout); | ||
_this2.socket.log("channel", "timeout " + _this2.topic + " (" + _this2.joinRef() + ")", _this2.joinPush.timeout); | ||
var leavePush = new Push(_this2, CHANNEL_EVENTS.leave, {}, _this2.timeout); | ||
leavePush.send(); | ||
_this2.state = CHANNEL_STATES.errored; | ||
_this2.joinPush.reset(); | ||
_this2.rejoinTimer.scheduleTimeout(); | ||
@@ -407,3 +462,3 @@ }); | ||
value: function join() { | ||
var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0]; | ||
var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout; | ||
@@ -450,3 +505,3 @@ if (this.joinedOnce) { | ||
value: function push(event, payload) { | ||
var timeout = arguments.length <= 2 || arguments[2] === undefined ? this.timeout : arguments[2]; | ||
var timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.timeout; | ||
@@ -467,14 +522,16 @@ if (!this.joinedOnce) { | ||
// 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: | ||
// | ||
// channel.leave().receive("ok", () => alert("left!") ) | ||
// | ||
/** 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: | ||
* | ||
* ```javascript | ||
* channel.leave().receive("ok", () => alert("left!") ) | ||
* ``` | ||
*/ | ||
@@ -486,3 +543,3 @@ }, { | ||
var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0]; | ||
var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout; | ||
@@ -492,3 +549,3 @@ this.state = CHANNEL_STATES.leaving; | ||
_this3.socket.log("channel", "leave " + _this3.topic); | ||
_this3.trigger(CHANNEL_EVENTS.close, "leave", _this3.joinRef()); | ||
_this3.trigger(CHANNEL_EVENTS.close, "leave"); | ||
}; | ||
@@ -509,8 +566,10 @@ var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout); | ||
// Overridable message hook | ||
// | ||
// Receives all events for specialized message handling | ||
// before dispatching to the channel callbacks. | ||
// | ||
// Must return the payload, modified or unmodified | ||
/** | ||
* Overridable message hook | ||
* | ||
* Receives all events for specialized message handling | ||
* before dispatching to the channel callbacks. | ||
* | ||
* Must return the payload, modified or unmodified | ||
*/ | ||
@@ -527,4 +586,14 @@ }, { | ||
key: "isMember", | ||
value: function isMember(topic) { | ||
return this.topic === topic; | ||
value: function isMember(topic, event, payload, joinRef) { | ||
if (this.topic !== topic) { | ||
return false; | ||
} | ||
var isLifecycleEvent = CHANNEL_LIFECYCLE_EVENTS.indexOf(event) >= 0; | ||
if (joinRef && isLifecycleEvent && joinRef !== this.joinRef()) { | ||
this.socket.log("channel", "dropping outdated message", { topic: topic, event: event, payload: payload, joinRef: joinRef }); | ||
return false; | ||
} else { | ||
return true; | ||
} | ||
} | ||
@@ -545,3 +614,3 @@ }, { | ||
value: function rejoin() { | ||
var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0]; | ||
var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout; | ||
if (this.isLeaving()) { | ||
@@ -554,12 +623,6 @@ return; | ||
key: "trigger", | ||
value: function trigger(event, payload, ref) { | ||
var close = CHANNEL_EVENTS.close; | ||
var error = CHANNEL_EVENTS.error; | ||
var leave = CHANNEL_EVENTS.leave; | ||
var join = CHANNEL_EVENTS.join; | ||
value: function trigger(event, payload, ref, joinRef) { | ||
var _this4 = this; | ||
if (ref && [close, error, leave, join].indexOf(event) >= 0 && ref !== this.joinRef()) { | ||
return; | ||
} | ||
var handledPayload = this.onMessage(event, payload, ref); | ||
var handledPayload = this.onMessage(event, payload, ref, joinRef); | ||
if (payload && !handledPayload) { | ||
@@ -572,3 +635,3 @@ throw "channel onMessage callbacks must return the payload, modified or unmodified"; | ||
}).map(function (bind) { | ||
return bind.callback(handledPayload, ref); | ||
return bind.callback(handledPayload, ref, joinRef || _this4.joinRef()); | ||
}); | ||
@@ -611,37 +674,80 @@ } | ||
var Socket = exports.Socket = function () { | ||
var Serializer = { | ||
encode: function encode(msg, callback) { | ||
var payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; | ||
return callback(JSON.stringify(payload)); | ||
}, | ||
decode: function decode(rawPayload, callback) { | ||
var _JSON$parse = JSON.parse(rawPayload), | ||
_JSON$parse2 = _slicedToArray(_JSON$parse, 5), | ||
join_ref = _JSON$parse2[0], | ||
ref = _JSON$parse2[1], | ||
topic = _JSON$parse2[2], | ||
event = _JSON$parse2[3], | ||
payload = _JSON$parse2[4]; | ||
// 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, for example WebSocket or Phoenix.LongPoll. | ||
// Defaults to WebSocket with automatic LongPoll fallback. | ||
// timeout - The default timeout in milliseconds to trigger push timeouts. | ||
// Defaults `DEFAULT_TIMEOUT` | ||
// heartbeatIntervalMs - The millisec interval to send a heartbeat message | ||
// reconnectAfterMs - The optional function that returns the millsec | ||
// reconnect interval. Defaults to stepped backoff of: | ||
// | ||
// function(tries){ | ||
// return [1000, 5000, 10000][tries - 1] || 10000 | ||
// } | ||
// | ||
// logger - The optional function for specialized logging, ie: | ||
// `logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) } | ||
// | ||
// longpollerTimeout - The maximum timeout of a long poll AJAX request. | ||
// Defaults to 20s (double the server long poll timer). | ||
// | ||
// params - The optional params to pass when connecting | ||
// | ||
// For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim) | ||
// | ||
return callback({ join_ref: join_ref, ref: ref, topic: topic, event: event, payload: payload }); | ||
} | ||
}; | ||
/** Initializes the Socket | ||
* | ||
* | ||
* For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim) | ||
* | ||
* @param {string} endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`, | ||
* `"wss://example.com"` | ||
* `"/socket"` (inherited host & protocol) | ||
* @param {Object} opts - Optional configuration | ||
* @param {string} opts.transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll. | ||
* | ||
* Defaults to WebSocket with automatic LongPoll fallback. | ||
* @param {Function} opts.encode - The function to encode outgoing messages. | ||
* | ||
* Defaults to JSON: | ||
* | ||
* ```javascript | ||
* (payload, callback) => callback(JSON.stringify(payload)) | ||
* ``` | ||
* | ||
* @param {Function} opts.decode - The function to decode incoming messages. | ||
* | ||
* Defaults to JSON: | ||
* | ||
* ```javascript | ||
* (payload, callback) => callback(JSON.parse(payload)) | ||
* ``` | ||
* | ||
* @param {number} opts.timeout - The default timeout in milliseconds to trigger push timeouts. | ||
* | ||
* Defaults `DEFAULT_TIMEOUT` | ||
* @param {number} opts.heartbeatIntervalMs - The millisec interval to send a heartbeat message | ||
* @param {number} opts.reconnectAfterMs - The optional function that returns the millsec reconnect interval. | ||
* | ||
* Defaults to stepped backoff of: | ||
* | ||
* ```javascript | ||
* function(tries){ | ||
* return [1000, 5000, 10000][tries - 1] || 10000 | ||
* } | ||
* ``` | ||
* @param {Function} opts.logger - The optional function for specialized logging, ie: | ||
* ```javascript | ||
* logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) } | ||
* ``` | ||
* | ||
* @param {number} opts.longpollerTimeout - The maximum timeout of a long poll AJAX request. | ||
* | ||
* Defaults to 20s (double the server long poll timer). | ||
* | ||
* @param {Object} opts.params - The optional params to pass when connecting | ||
* | ||
* | ||
*/ | ||
var Socket = exports.Socket = function () { | ||
function Socket(endPoint) { | ||
var _this4 = this; | ||
var _this5 = this; | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -656,2 +762,11 @@ _classCallCheck(this, Socket); | ||
this.transport = opts.transport || window.WebSocket || LongPoll; | ||
this.defaultEncoder = Serializer.encode; | ||
this.defaultDecoder = Serializer.decode; | ||
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; | ||
} | ||
this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000; | ||
@@ -665,5 +780,7 @@ this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) { | ||
this.endPoint = endPoint + "/" + TRANSPORTS.websocket; | ||
this.heartbeatTimer = null; | ||
this.pendingHeartbeatRef = null; | ||
this.reconnectTimer = new Timer(function () { | ||
_this4.disconnect(function () { | ||
return _this4.connect(); | ||
_this5.disconnect(function () { | ||
return _this5.connect(); | ||
}); | ||
@@ -706,3 +823,6 @@ }, this.reconnectAfterMs); | ||
// params - The params to send when connecting, for example `{user_id: userToken}` | ||
/** | ||
* | ||
* @param {Object} params - The params to send when connecting, for example `{user_id: userToken}` | ||
*/ | ||
@@ -712,3 +832,3 @@ }, { | ||
value: function connect(params) { | ||
var _this5 = this; | ||
var _this6 = this; | ||
@@ -726,16 +846,21 @@ if (params) { | ||
this.conn.onopen = function () { | ||
return _this5.onConnOpen(); | ||
return _this6.onConnOpen(); | ||
}; | ||
this.conn.onerror = function (error) { | ||
return _this5.onConnError(error); | ||
return _this6.onConnError(error); | ||
}; | ||
this.conn.onmessage = function (event) { | ||
return _this5.onConnMessage(event); | ||
return _this6.onConnMessage(event); | ||
}; | ||
this.conn.onclose = function (event) { | ||
return _this5.onConnClose(event); | ||
return _this6.onConnClose(event); | ||
}; | ||
} | ||
// Logs the message. Override `this.logger` for specialized logging. noops by default | ||
/** | ||
* Logs the message. Override `this.logger` for specialized logging. noops by default | ||
* @param {string} kind | ||
* @param {string} msg | ||
* @param {Object} data | ||
*/ | ||
@@ -778,3 +903,3 @@ }, { | ||
value: function onConnOpen() { | ||
var _this6 = this; | ||
var _this7 = this; | ||
@@ -787,3 +912,3 @@ this.log("transport", "connected to " + this.endPointURL()); | ||
this.heartbeatTimer = setInterval(function () { | ||
return _this6.sendHeartbeat(); | ||
return _this7.sendHeartbeat(); | ||
}, this.heartbeatIntervalMs); | ||
@@ -851,3 +976,3 @@ } | ||
value: function channel(topic) { | ||
var chanParams = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var chanParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -861,13 +986,16 @@ var chan = new Channel(topic, chanParams, this); | ||
value: function push(data) { | ||
var _this7 = this; | ||
var _this8 = this; | ||
var topic = data.topic; | ||
var event = data.event; | ||
var payload = data.payload; | ||
var ref = data.ref; | ||
var topic = data.topic, | ||
event = data.event, | ||
payload = data.payload, | ||
ref = data.ref, | ||
join_ref = data.join_ref; | ||
var callback = function callback() { | ||
return _this7.conn.send(JSON.stringify(data)); | ||
_this8.encode(data, function (result) { | ||
_this8.conn.send(result); | ||
}); | ||
}; | ||
this.log("push", topic + " " + event + " (" + ref + ")", payload); | ||
this.log("push", topic + " " + event + " (" + join_ref + ", " + ref + ")", payload); | ||
if (this.isConnected()) { | ||
@@ -880,3 +1008,5 @@ callback(); | ||
// Return the next message ref, accounting for overflows | ||
/** | ||
* Return the next message ref, accounting for overflows | ||
*/ | ||
@@ -901,3 +1031,10 @@ }, { | ||
} | ||
this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef() }); | ||
if (this.pendingHeartbeatRef) { | ||
this.pendingHeartbeatRef = null; | ||
this.log("transport", "heartbeat timeout. Attempting to re-establish connection"); | ||
this.conn.close(WS_CLOSE_NORMAL, "hearbeat timeout"); | ||
return; | ||
} | ||
this.pendingHeartbeatRef = this.makeRef(); | ||
this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef }); | ||
} | ||
@@ -917,17 +1054,25 @@ }, { | ||
value: function onConnMessage(rawMessage) { | ||
var msg = JSON.parse(rawMessage.data); | ||
var topic = msg.topic; | ||
var event = msg.event; | ||
var payload = msg.payload; | ||
var ref = msg.ref; | ||
var _this9 = this; | ||
this.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload); | ||
this.channels.filter(function (channel) { | ||
return channel.isMember(topic); | ||
}).forEach(function (channel) { | ||
return channel.trigger(event, payload, ref); | ||
this.decode(rawMessage.data, function (msg) { | ||
var topic = msg.topic, | ||
event = msg.event, | ||
payload = msg.payload, | ||
ref = msg.ref, | ||
join_ref = msg.join_ref; | ||
if (ref && ref === _this9.pendingHeartbeatRef) { | ||
_this9.pendingHeartbeatRef = null; | ||
} | ||
_this9.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload); | ||
_this9.channels.filter(function (channel) { | ||
return channel.isMember(topic, event, payload, join_ref); | ||
}).forEach(function (channel) { | ||
return channel.trigger(event, payload, ref, join_ref); | ||
}); | ||
_this9.stateChangeCallbacks.message.forEach(function (callback) { | ||
return callback(msg); | ||
}); | ||
}); | ||
this.stateChangeCallbacks.message.forEach(function (callback) { | ||
return callback(msg); | ||
}); | ||
} | ||
@@ -981,3 +1126,3 @@ }]); | ||
value: function poll() { | ||
var _this8 = this; | ||
var _this10 = this; | ||
@@ -990,7 +1135,7 @@ if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) { | ||
if (resp) { | ||
var status = resp.status; | ||
var token = resp.token; | ||
var messages = resp.messages; | ||
var status = resp.status, | ||
token = resp.token, | ||
messages = resp.messages; | ||
_this8.token = token; | ||
_this10.token = token; | ||
} else { | ||
@@ -1003,18 +1148,18 @@ var status = 0; | ||
messages.forEach(function (msg) { | ||
return _this8.onmessage({ data: JSON.stringify(msg) }); | ||
return _this10.onmessage({ data: msg }); | ||
}); | ||
_this8.poll(); | ||
_this10.poll(); | ||
break; | ||
case 204: | ||
_this8.poll(); | ||
_this10.poll(); | ||
break; | ||
case 410: | ||
_this8.readyState = SOCKET_STATES.open; | ||
_this8.onopen(); | ||
_this8.poll(); | ||
_this10.readyState = SOCKET_STATES.open; | ||
_this10.onopen(); | ||
_this10.poll(); | ||
break; | ||
case 0: | ||
case 500: | ||
_this8.onerror(); | ||
_this8.closeAndRetry(); | ||
_this10.onerror(); | ||
_this10.closeAndRetry(); | ||
break; | ||
@@ -1029,8 +1174,8 @@ default: | ||
value: function send(body) { | ||
var _this9 = this; | ||
var _this11 = this; | ||
Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) { | ||
if (!resp || resp.status !== 200) { | ||
_this9.onerror(status); | ||
_this9.closeAndRetry(); | ||
_this11.onerror(resp && resp.status); | ||
_this11.closeAndRetry(); | ||
} | ||
@@ -1062,5 +1207,5 @@ }); | ||
} else { | ||
var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari | ||
var _req = window.XMLHttpRequest ? new window.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari | ||
new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5 | ||
this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback); | ||
this.xhrRequest(_req, method, endPoint, accept, body, timeout, ontimeout, callback); | ||
} | ||
@@ -1071,3 +1216,3 @@ } | ||
value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) { | ||
var _this10 = this; | ||
var _this12 = this; | ||
@@ -1077,3 +1222,3 @@ req.timeout = timeout; | ||
req.onload = function () { | ||
var response = _this10.parseJSON(req.responseText); | ||
var response = _this12.parseJSON(req.responseText); | ||
callback && callback(response); | ||
@@ -1093,6 +1238,6 @@ }; | ||
value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) { | ||
var _this11 = this; | ||
var _this13 = this; | ||
req.open(method, endPoint, true); | ||
req.timeout = timeout; | ||
req.open(method, endPoint, true); | ||
req.setRequestHeader("Content-Type", accept); | ||
@@ -1103,4 +1248,4 @@ req.onerror = function () { | ||
req.onreadystatechange = function () { | ||
if (req.readyState === _this11.states.complete && callback) { | ||
var response = _this11.parseJSON(req.responseText); | ||
if (req.readyState === _this13.states.complete && callback) { | ||
var response = _this13.parseJSON(req.responseText); | ||
callback(response); | ||
@@ -1118,3 +1263,12 @@ } | ||
value: function parseJSON(resp) { | ||
return resp && resp !== "" ? JSON.parse(resp) : null; | ||
if (!resp || resp === "") { | ||
return null; | ||
} | ||
try { | ||
return JSON.parse(resp); | ||
} catch (e) { | ||
console && console.log("failed to parse JSON response", resp); | ||
return null; | ||
} | ||
} | ||
@@ -1158,3 +1312,3 @@ }, { | ||
syncState: function syncState(currentState, newState, onJoin, onLeave) { | ||
var _this12 = this; | ||
var _this14 = this; | ||
@@ -1173,24 +1327,22 @@ var state = this.clone(currentState); | ||
if (currentPresence) { | ||
(function () { | ||
var newRefs = newPresence.metas.map(function (m) { | ||
return m.phx_ref; | ||
}); | ||
var curRefs = currentPresence.metas.map(function (m) { | ||
return m.phx_ref; | ||
}); | ||
var joinedMetas = newPresence.metas.filter(function (m) { | ||
return curRefs.indexOf(m.phx_ref) < 0; | ||
}); | ||
var leftMetas = currentPresence.metas.filter(function (m) { | ||
return newRefs.indexOf(m.phx_ref) < 0; | ||
}); | ||
if (joinedMetas.length > 0) { | ||
joins[key] = newPresence; | ||
joins[key].metas = joinedMetas; | ||
} | ||
if (leftMetas.length > 0) { | ||
leaves[key] = _this12.clone(currentPresence); | ||
leaves[key].metas = leftMetas; | ||
} | ||
})(); | ||
var newRefs = newPresence.metas.map(function (m) { | ||
return m.phx_ref; | ||
}); | ||
var curRefs = currentPresence.metas.map(function (m) { | ||
return m.phx_ref; | ||
}); | ||
var joinedMetas = newPresence.metas.filter(function (m) { | ||
return curRefs.indexOf(m.phx_ref) < 0; | ||
}); | ||
var leftMetas = currentPresence.metas.filter(function (m) { | ||
return newRefs.indexOf(m.phx_ref) < 0; | ||
}); | ||
if (joinedMetas.length > 0) { | ||
joins[key] = newPresence; | ||
joins[key].metas = joinedMetas; | ||
} | ||
if (leftMetas.length > 0) { | ||
leaves[key] = _this14.clone(currentPresence); | ||
leaves[key].metas = leftMetas; | ||
} | ||
} else { | ||
@@ -1203,4 +1355,4 @@ joins[key] = newPresence; | ||
syncDiff: function syncDiff(currentState, _ref2, onJoin, onLeave) { | ||
var joins = _ref2.joins; | ||
var leaves = _ref2.leaves; | ||
var joins = _ref2.joins, | ||
leaves = _ref2.leaves; | ||
@@ -1255,2 +1407,3 @@ var state = this.clone(currentState); | ||
// private | ||
@@ -1268,15 +1421,21 @@ | ||
// Creates a timer that accepts a `timerCalc` function to perform | ||
// calculated timeout retries, such as exponential backoff. | ||
// | ||
// ## Examples | ||
// | ||
// let reconnectTimer = new Timer(() => this.connect(), function(tries){ | ||
// return [1000, 5000, 10000][tries - 1] || 10000 | ||
// }) | ||
// reconnectTimer.scheduleTimeout() // fires after 1000 | ||
// reconnectTimer.scheduleTimeout() // fires after 5000 | ||
// reconnectTimer.reset() | ||
// reconnectTimer.scheduleTimeout() // fires after 1000 | ||
// | ||
/** | ||
* | ||
* Creates a timer that accepts a `timerCalc` function to perform | ||
* calculated timeout retries, such as exponential backoff. | ||
* | ||
* ## Examples | ||
* | ||
* ```javascript | ||
* let reconnectTimer = new Timer(() => this.connect(), function(tries){ | ||
* return [1000, 5000, 10000][tries - 1] || 10000 | ||
* }) | ||
* reconnectTimer.scheduleTimeout() // fires after 1000 | ||
* reconnectTimer.scheduleTimeout() // fires after 5000 | ||
* reconnectTimer.reset() | ||
* reconnectTimer.scheduleTimeout() // fires after 1000 | ||
* ``` | ||
* @param {Function} callback | ||
* @param {Function} timerCalc | ||
*/ | ||
@@ -1300,3 +1459,5 @@ var Timer = function () { | ||
// Cancels any previous scheduleTimeout and schedules callback | ||
/** | ||
* Cancels any previous scheduleTimeout and schedules callback | ||
*/ | ||
@@ -1306,3 +1467,3 @@ }, { | ||
value: function scheduleTimeout() { | ||
var _this13 = this; | ||
var _this15 = this; | ||
@@ -1312,4 +1473,4 @@ clearTimeout(this.timer); | ||
this.timer = setTimeout(function () { | ||
_this13.tries = _this13.tries + 1; | ||
_this13.callback(); | ||
_this15.tries = _this15.tries + 1; | ||
_this15.callback(); | ||
}, this.timerCalc(this.tries + 1)); | ||
@@ -1322,3 +1483,2 @@ } | ||
})(typeof(exports) === "undefined" ? window.Phoenix = window.Phoenix || {} : exports); | ||
}))); |
@@ -16,2 +16,4 @@ ![phoenix logo](https://raw.githubusercontent.com/phoenixframework/phoenix/master/priv/static/phoenix.png) | ||
Phoenix.js documentation is available at [https://hexdocs.pm/phoenix/js](https://hexdocs.pm/phoenix/js) | ||
## Contributing | ||
@@ -23,15 +25,16 @@ | ||
In order to create a new project using the latest Phoenix source installer (the `phoenix.new` Mix task) you will need to ensure two things. | ||
You can create a new project using the latest Phoenix source installer (the `phx.new` Mix task) with the following steps: | ||
1. Remove any previously installed `phoenix_new` archives so that Mix will pick up the local source code. This can be done with `mix archive.uninstall phoenix_new.ez` or by simply deleting the file, which is usually in `~/.mix/archives/`. | ||
2. Run the command from within the `installer` directory and provide a subdirectory within the installer to generate your dev project. The command below will create a new project using your current Phoenix checkout, thanks to the `--dev` flag. | ||
1. Remove any previously installed `phx_new` archives so that Mix will pick up the local source code. This can be done with `mix archive.uninstall phx_new` or by simply deleting the file, which is usually in `~/.mix/archives/`. | ||
2. Copy this repo via `git clone https://github.com/phoenixframework/phoenix` or by downloading it | ||
3. Run the `phx.new` mix task from within the `installer` directory, for example: | ||
```bash | ||
$ cd installer | ||
$ mix phoenix.new dev_app --dev | ||
$ mix phx.new dev_app --dev | ||
``` | ||
This will produce a new project that has `:phoenix` configured as a relative dependency: | ||
The `--dev` flag will configure your new project's `:phoenix` dep as a relative path dependency, pointing to your local Phoenix checkout: | ||
``` | ||
```elixir | ||
defp deps do | ||
@@ -41,3 +44,3 @@ [{:phoenix, path: "../..", override: true}, | ||
The command must be run from the `installer` directory. See the discussion in [PR 1224](https://github.com/phoenixframework/phoenix/pull/1224) for more information. | ||
To create projects outside of the `installer/` directory, add the latest archive to your machine by following the instructions in [installer/README.md](https://github.com/phoenixframework/phoenix/blob/master/installer/README.md) | ||
@@ -48,4 +51,3 @@ ### Building phoenix.js | ||
$ npm install | ||
$ npm install -g brunch | ||
$ brunch watch | ||
$ npm run watch | ||
``` | ||
@@ -66,2 +68,3 @@ | ||
* [phoenix-core Mailing list (development)][6] | ||
* Visit Phoenix's sponsor, DockYard, for expert [phoenix consulting](https://dockyard.com/phoenix-consulting) | ||
* Privately disclose security vulnerabilities to phoenix-security@googlegroups.com | ||
@@ -68,0 +71,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
122312
2268
79
9
1