Socket
Socket
Sign inDemoInstall

phoenix

Package Overview
Dependencies
0
Maintainers
2
Versions
79
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.2.1 to 1.3.0

assets/js/phoenix.js

45

CHANGELOG.md
# 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 @@

22

package.json
{
"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"
}
}

886

priv/static/phoenix.js

@@ -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 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc