phoenix-socket
Advanced tools
Comparing version 1.1.3 to 1.2.3
"use strict"; | ||
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", { | ||
@@ -9,4 +7,8 @@ value: true | ||
function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; } | ||
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 _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); } } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
@@ -19,3 +21,3 @@ | ||
// A single connection is established to the server and | ||
// channels are mulitplexed over the connection. | ||
// channels are multiplexed over the connection. | ||
// Connect to the server using the `Socket` class: | ||
@@ -40,3 +42,3 @@ // | ||
// | ||
// let channel = socket.channel("rooms:123", {token: roomToken}) | ||
// let channel = socket.channel("room:123", {token: roomToken}) | ||
// channel.on("new_msg", msg => console.log("Got message", msg) ) | ||
@@ -64,3 +66,12 @@ // $input.onEnter( e => { | ||
// | ||
// ## 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 | ||
@@ -95,3 +106,3 @@ // | ||
// `onError` hooks are invoked if the socket connection drops, or the channel | ||
// crashes on the server. In either case, a channel rejoin is attemtped | ||
// crashes on the server. In either case, a channel rejoin is attempted | ||
// automatically in an exponential backoff manner. | ||
@@ -105,3 +116,74 @@ // | ||
// | ||
// | ||
// ## 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("presences", 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"; | ||
@@ -114,3 +196,4 @@ var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; | ||
joined: "joined", | ||
joining: "joining" | ||
joining: "joining", | ||
leaving: "leaving" | ||
}; | ||
@@ -129,3 +212,3 @@ var CHANNEL_EVENTS = { | ||
var Push = (function () { | ||
var Push = function () { | ||
@@ -139,3 +222,2 @@ // Initializes the Push | ||
// | ||
function Push(channel, event, payload, timeout) { | ||
@@ -196,5 +278,5 @@ _classCallCheck(this, Push); | ||
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; | ||
@@ -256,5 +338,5 @@ this.recHooks.filter(function (h) { | ||
return Push; | ||
})(); | ||
}(); | ||
var Channel = exports.Channel = (function () { | ||
var Channel = exports.Channel = function () { | ||
function Channel(topic, params, socket) { | ||
@@ -286,3 +368,4 @@ var _this2 = this; | ||
this.onClose(function () { | ||
_this2.socket.log("channel", "close " + _this2.topic); | ||
_this2.rejoinTimer.reset(); | ||
_this2.socket.log("channel", "close " + _this2.topic + " " + _this2.joinRef()); | ||
_this2.state = CHANNEL_STATES.closed; | ||
@@ -292,2 +375,5 @@ _this2.socket.remove(_this2); | ||
this.onError(function (reason) { | ||
if (_this2.isLeaving() || _this2.isClosed()) { | ||
return; | ||
} | ||
_this2.socket.log("channel", "error " + _this2.topic, reason); | ||
@@ -298,6 +384,5 @@ _this2.state = CHANNEL_STATES.errored; | ||
this.joinPush.receive("timeout", function () { | ||
if (_this2.state !== CHANNEL_STATES.joining) { | ||
if (!_this2.isJoining()) { | ||
return; | ||
} | ||
_this2.socket.log("channel", "timeout " + _this2.topic, _this2.joinPush.timeout); | ||
@@ -323,3 +408,3 @@ _this2.state = CHANNEL_STATES.errored; | ||
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; | ||
@@ -330,5 +415,5 @@ if (this.joinedOnce) { | ||
this.joinedOnce = true; | ||
this.rejoin(timeout); | ||
return this.joinPush; | ||
} | ||
this.rejoin(timeout); | ||
return this.joinPush; | ||
} | ||
@@ -362,3 +447,3 @@ }, { | ||
value: function canPush() { | ||
return this.socket.isConnected() && this.state === CHANNEL_STATES.joined; | ||
return this.socket.isConnected() && this.isJoined(); | ||
} | ||
@@ -368,3 +453,3 @@ }, { | ||
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; | ||
@@ -403,7 +488,8 @@ if (!this.joinedOnce) { | ||
var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0]; | ||
var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout; | ||
this.state = CHANNEL_STATES.leaving; | ||
var onClose = function onClose() { | ||
_this3.socket.log("channel", "leave " + _this3.topic); | ||
_this3.trigger(CHANNEL_EVENTS.close, "leave"); | ||
_this3.trigger(CHANNEL_EVENTS.close, "leave", _this3.joinRef()); | ||
}; | ||
@@ -427,6 +513,11 @@ var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout); | ||
// Receives all events for specialized message handling | ||
// before dispatching to the channel callbacks. | ||
// | ||
// Must return the payload, modified or unmodified | ||
}, { | ||
key: "onMessage", | ||
value: function onMessage(event, payload, ref) {} | ||
value: function onMessage(event, payload, ref) { | ||
return payload; | ||
} | ||
@@ -441,2 +532,7 @@ // private | ||
}, { | ||
key: "joinRef", | ||
value: function joinRef() { | ||
return this.joinPush.ref; | ||
} | ||
}, { | ||
key: "sendJoin", | ||
@@ -450,3 +546,6 @@ value: function sendJoin(timeout) { | ||
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()) { | ||
return; | ||
} | ||
this.sendJoin(timeout); | ||
@@ -456,8 +555,20 @@ } | ||
key: "trigger", | ||
value: function trigger(triggerEvent, payload, ref) { | ||
this.onMessage(triggerEvent, payload, ref); | ||
value: function trigger(event, payload, ref) { | ||
var close = CHANNEL_EVENTS.close, | ||
error = CHANNEL_EVENTS.error, | ||
leave = CHANNEL_EVENTS.leave, | ||
join = CHANNEL_EVENTS.join; | ||
if (ref && [close, error, leave, join].indexOf(event) >= 0 && ref !== this.joinRef()) { | ||
return; | ||
} | ||
var handledPayload = this.onMessage(event, payload, ref); | ||
if (payload && !handledPayload) { | ||
throw "channel onMessage callbacks must return the payload, modified or unmodified"; | ||
} | ||
this.bindings.filter(function (bind) { | ||
return bind.event === triggerEvent; | ||
return bind.event === event; | ||
}).map(function (bind) { | ||
return bind.callback(payload, ref); | ||
return bind.callback(handledPayload, ref); | ||
}); | ||
@@ -470,8 +581,33 @@ } | ||
} | ||
}, { | ||
key: "isClosed", | ||
value: function isClosed() { | ||
return this.state === CHANNEL_STATES.closed; | ||
} | ||
}, { | ||
key: "isErrored", | ||
value: function isErrored() { | ||
return this.state === CHANNEL_STATES.errored; | ||
} | ||
}, { | ||
key: "isJoined", | ||
value: function isJoined() { | ||
return this.state === CHANNEL_STATES.joined; | ||
} | ||
}, { | ||
key: "isJoining", | ||
value: function isJoining() { | ||
return this.state === CHANNEL_STATES.joining; | ||
} | ||
}, { | ||
key: "isLeaving", | ||
value: function isLeaving() { | ||
return this.state === CHANNEL_STATES.leaving; | ||
} | ||
}]); | ||
return Channel; | ||
})(); | ||
}(); | ||
var Socket = exports.Socket = (function () { | ||
var Socket = exports.Socket = function () { | ||
@@ -506,7 +642,6 @@ // Initializes the Socket | ||
// | ||
function Socket(endPoint) { | ||
var _this4 = this; | ||
var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
@@ -703,3 +838,3 @@ _classCallCheck(this, Socket); | ||
this.channels = this.channels.filter(function (c) { | ||
return !c.isMember(channel.topic); | ||
return c.joinRef() !== channel.joinRef(); | ||
}); | ||
@@ -710,3 +845,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] : {}; | ||
@@ -722,6 +857,6 @@ var chan = new Channel(topic, chanParams, 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; | ||
@@ -775,6 +910,6 @@ var callback = function callback() { | ||
var msg = JSON.parse(rawMessage.data); | ||
var topic = msg.topic; | ||
var event = msg.event; | ||
var payload = msg.payload; | ||
var ref = msg.ref; | ||
var topic = msg.topic, | ||
event = msg.event, | ||
payload = msg.payload, | ||
ref = msg.ref; | ||
@@ -794,5 +929,5 @@ this.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload); | ||
return Socket; | ||
})(); | ||
}(); | ||
var LongPoll = exports.LongPoll = (function () { | ||
var LongPoll = exports.LongPoll = function () { | ||
function LongPoll(endPoint) { | ||
@@ -847,5 +982,5 @@ _classCallCheck(this, LongPoll); | ||
if (resp) { | ||
var status = resp.status; | ||
var token = resp.token; | ||
var messages = resp.messages; | ||
var status = resp.status, | ||
token = resp.token, | ||
messages = resp.messages; | ||
@@ -903,5 +1038,5 @@ _this8.token = token; | ||
return LongPoll; | ||
})(); | ||
}(); | ||
var Ajax = exports.Ajax = (function () { | ||
var Ajax = exports.Ajax = function () { | ||
function Ajax() { | ||
@@ -918,5 +1053,5 @@ _classCallCheck(this, Ajax); | ||
} else { | ||
var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari | ||
var _req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari | ||
new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5 | ||
this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback); | ||
this.xhrRequest(_req, method, endPoint, accept, body, timeout, ontimeout, callback); | ||
} | ||
@@ -1003,6 +1138,113 @@ } | ||
return Ajax; | ||
})(); | ||
}(); | ||
Ajax.states = { complete: 4 }; | ||
var Presence = exports.Presence = { | ||
syncState: function syncState(currentState, newState, onJoin, onLeave) { | ||
var _this12 = this; | ||
var state = this.clone(currentState); | ||
var joins = {}; | ||
var leaves = {}; | ||
this.map(state, function (key, presence) { | ||
if (!newState[key]) { | ||
leaves[key] = presence; | ||
} | ||
}); | ||
this.map(newState, function (key, newPresence) { | ||
var currentPresence = state[key]; | ||
if (currentPresence) { | ||
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; | ||
} | ||
} else { | ||
joins[key] = newPresence; | ||
} | ||
}); | ||
return this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave); | ||
}, | ||
syncDiff: function syncDiff(currentState, _ref2, onJoin, onLeave) { | ||
var joins = _ref2.joins, | ||
leaves = _ref2.leaves; | ||
var state = this.clone(currentState); | ||
if (!onJoin) { | ||
onJoin = function onJoin() {}; | ||
} | ||
if (!onLeave) { | ||
onLeave = function onLeave() {}; | ||
} | ||
this.map(joins, function (key, newPresence) { | ||
var currentPresence = state[key]; | ||
state[key] = newPresence; | ||
if (currentPresence) { | ||
var _state$key$metas; | ||
(_state$key$metas = state[key].metas).unshift.apply(_state$key$metas, _toConsumableArray(currentPresence.metas)); | ||
} | ||
onJoin(key, currentPresence, newPresence); | ||
}); | ||
this.map(leaves, function (key, leftPresence) { | ||
var currentPresence = state[key]; | ||
if (!currentPresence) { | ||
return; | ||
} | ||
var refsToRemove = leftPresence.metas.map(function (m) { | ||
return m.phx_ref; | ||
}); | ||
currentPresence.metas = currentPresence.metas.filter(function (p) { | ||
return refsToRemove.indexOf(p.phx_ref) < 0; | ||
}); | ||
onLeave(key, currentPresence, leftPresence); | ||
if (currentPresence.metas.length === 0) { | ||
delete state[key]; | ||
} | ||
}); | ||
return state; | ||
}, | ||
list: function list(presences, chooser) { | ||
if (!chooser) { | ||
chooser = function chooser(key, pres) { | ||
return pres; | ||
}; | ||
} | ||
return this.map(presences, function (key, presence) { | ||
return chooser(key, presence); | ||
}); | ||
}, | ||
// private | ||
map: function map(obj, func) { | ||
return Object.getOwnPropertyNames(obj).map(function (key) { | ||
return func(key, obj[key]); | ||
}); | ||
}, | ||
clone: function clone(obj) { | ||
return JSON.parse(JSON.stringify(obj)); | ||
} | ||
}; | ||
// Creates a timer that accepts a `timerCalc` function to perform | ||
@@ -1022,3 +1264,3 @@ // calculated timeout retries, such as exponential backoff. | ||
var Timer = (function () { | ||
var Timer = function () { | ||
function Timer(callback, timerCalc) { | ||
@@ -1045,3 +1287,3 @@ _classCallCheck(this, Timer); | ||
value: function scheduleTimeout() { | ||
var _this12 = this; | ||
var _this13 = this; | ||
@@ -1051,4 +1293,4 @@ clearTimeout(this.timer); | ||
this.timer = setTimeout(function () { | ||
_this12.tries = _this12.tries + 1; | ||
_this12.callback(); | ||
_this13.tries = _this13.tries + 1; | ||
_this13.callback(); | ||
}, this.timerCalc(this.tries + 1)); | ||
@@ -1059,2 +1301,2 @@ } | ||
return Timer; | ||
})(); | ||
}(); |
{ | ||
"name": "phoenix-socket", | ||
"version": "1.1.3", | ||
"version": "1.2.3", | ||
"description": "Socket API for accessing Phoenix Framework's Channels", | ||
@@ -5,0 +5,0 @@ "main": "dist/socket.js", |
@@ -6,3 +6,3 @@ // Phoenix Channels JavaScript client | ||
// A single connection is established to the server and | ||
// channels are mulitplexed over the connection. | ||
// channels are multiplexed over the connection. | ||
// Connect to the server using the `Socket` class: | ||
@@ -27,3 +27,3 @@ // | ||
// | ||
// let channel = socket.channel("rooms:123", {token: roomToken}) | ||
// let channel = socket.channel("room:123", {token: roomToken}) | ||
// channel.on("new_msg", msg => console.log("Got message", msg) ) | ||
@@ -51,3 +51,12 @@ // $input.onEnter( e => { | ||
// | ||
// ## 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 | ||
@@ -82,3 +91,3 @@ // | ||
// `onError` hooks are invoked if the socket connection drops, or the channel | ||
// crashes on the server. In either case, a channel rejoin is attemtped | ||
// crashes on the server. In either case, a channel rejoin is attempted | ||
// automatically in an exponential backoff manner. | ||
@@ -92,3 +101,74 @@ // | ||
// | ||
// | ||
// ## 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("presences", 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)}) | ||
// }) | ||
// | ||
const VSN = "1.0.0" | ||
@@ -102,2 +182,3 @@ const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3} | ||
joining: "joining", | ||
leaving: "leaving", | ||
} | ||
@@ -230,7 +311,8 @@ const CHANNEL_EVENTS = { | ||
this.onClose( () => { | ||
this.socket.log("channel", `close ${this.topic}`) | ||
this.rejoinTimer.reset() | ||
this.socket.log("channel", `close ${this.topic} ${this.joinRef()}`) | ||
this.state = CHANNEL_STATES.closed | ||
this.socket.remove(this) | ||
}) | ||
this.onError( reason => { | ||
this.onError( reason => { if(this.isLeaving() || this.isClosed()){ return } | ||
this.socket.log("channel", `error ${this.topic}`, reason) | ||
@@ -240,5 +322,3 @@ this.state = CHANNEL_STATES.errored | ||
}) | ||
this.joinPush.receive("timeout", () => { | ||
if(this.state !== CHANNEL_STATES.joining){ return } | ||
this.joinPush.receive("timeout", () => { if(!this.isJoining()){ return } | ||
this.socket.log("channel", `timeout ${this.topic}`, this.joinPush.timeout) | ||
@@ -265,5 +345,5 @@ this.state = CHANNEL_STATES.errored | ||
this.joinedOnce = true | ||
this.rejoin(timeout) | ||
return this.joinPush | ||
} | ||
this.rejoin(timeout) | ||
return this.joinPush | ||
} | ||
@@ -281,3 +361,3 @@ | ||
canPush(){ return this.socket.isConnected() && this.state === CHANNEL_STATES.joined } | ||
canPush(){ return this.socket.isConnected() && this.isJoined() } | ||
@@ -312,5 +392,6 @@ push(event, payload, timeout = this.timeout){ | ||
leave(timeout = this.timeout){ | ||
this.state = CHANNEL_STATES.leaving | ||
let onClose = () => { | ||
this.socket.log("channel", `leave ${this.topic}`) | ||
this.trigger(CHANNEL_EVENTS.close, "leave") | ||
this.trigger(CHANNEL_EVENTS.close, "leave", this.joinRef()) | ||
} | ||
@@ -329,3 +410,6 @@ let leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout) | ||
// Receives all events for specialized message handling | ||
onMessage(event, payload, ref){} | ||
// before dispatching to the channel callbacks. | ||
// | ||
// Must return the payload, modified or unmodified | ||
onMessage(event, payload, ref){ return payload } | ||
@@ -336,2 +420,4 @@ // private | ||
joinRef(){ return this.joinPush.ref } | ||
sendJoin(timeout){ | ||
@@ -342,11 +428,25 @@ this.state = CHANNEL_STATES.joining | ||
rejoin(timeout = this.timeout){ this.sendJoin(timeout) } | ||
rejoin(timeout = this.timeout){ if(this.isLeaving()){ return } | ||
this.sendJoin(timeout) | ||
} | ||
trigger(triggerEvent, payload, ref){ | ||
this.onMessage(triggerEvent, payload, ref) | ||
this.bindings.filter( bind => bind.event === triggerEvent ) | ||
.map( bind => bind.callback(payload, ref) ) | ||
trigger(event, payload, ref){ | ||
let {close, error, leave, join} = CHANNEL_EVENTS | ||
if(ref && [close, error, leave, join].indexOf(event) >= 0 && ref !== this.joinRef()){ | ||
return | ||
} | ||
let handledPayload = this.onMessage(event, payload, ref) | ||
if(payload && !handledPayload){ throw("channel onMessage callbacks must return the payload, modified or unmodified") } | ||
this.bindings.filter( bind => bind.event === event) | ||
.map( bind => bind.callback(handledPayload, ref)) | ||
} | ||
replyEventName(ref){ return `chan_reply_${ref}` } | ||
isClosed() { return this.state === CHANNEL_STATES.closed } | ||
isErrored(){ return this.state === CHANNEL_STATES.errored } | ||
isJoined() { return this.state === CHANNEL_STATES.joined } | ||
isJoining(){ return this.state === CHANNEL_STATES.joining } | ||
isLeaving(){ return this.state === CHANNEL_STATES.leaving } | ||
} | ||
@@ -495,3 +595,3 @@ | ||
remove(channel){ | ||
this.channels = this.channels.filter( c => !c.isMember(channel.topic) ) | ||
this.channels = this.channels.filter(c => c.joinRef() !== channel.joinRef()) | ||
} | ||
@@ -710,2 +810,83 @@ | ||
export var Presence = { | ||
syncState(currentState, newState, onJoin, onLeave){ | ||
let state = this.clone(currentState) | ||
let joins = {} | ||
let leaves = {} | ||
this.map(state, (key, presence) => { | ||
if(!newState[key]){ | ||
leaves[key] = presence | ||
} | ||
}) | ||
this.map(newState, (key, newPresence) => { | ||
let currentPresence = state[key] | ||
if(currentPresence){ | ||
let newRefs = newPresence.metas.map(m => m.phx_ref) | ||
let curRefs = currentPresence.metas.map(m => m.phx_ref) | ||
let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0) | ||
let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0) | ||
if(joinedMetas.length > 0){ | ||
joins[key] = newPresence | ||
joins[key].metas = joinedMetas | ||
} | ||
if(leftMetas.length > 0){ | ||
leaves[key] = this.clone(currentPresence) | ||
leaves[key].metas = leftMetas | ||
} | ||
} else { | ||
joins[key] = newPresence | ||
} | ||
}) | ||
return this.syncDiff(state, {joins: joins, leaves: leaves}, onJoin, onLeave) | ||
}, | ||
syncDiff(currentState, {joins, leaves}, onJoin, onLeave){ | ||
let state = this.clone(currentState) | ||
if(!onJoin){ onJoin = function(){} } | ||
if(!onLeave){ onLeave = function(){} } | ||
this.map(joins, (key, newPresence) => { | ||
let currentPresence = state[key] | ||
state[key] = newPresence | ||
if(currentPresence){ | ||
state[key].metas.unshift(...currentPresence.metas) | ||
} | ||
onJoin(key, currentPresence, newPresence) | ||
}) | ||
this.map(leaves, (key, leftPresence) => { | ||
let currentPresence = state[key] | ||
if(!currentPresence){ return } | ||
let refsToRemove = leftPresence.metas.map(m => m.phx_ref) | ||
currentPresence.metas = currentPresence.metas.filter(p => { | ||
return refsToRemove.indexOf(p.phx_ref) < 0 | ||
}) | ||
onLeave(key, currentPresence, leftPresence) | ||
if(currentPresence.metas.length === 0){ | ||
delete state[key] | ||
} | ||
}) | ||
return state | ||
}, | ||
list(presences, chooser){ | ||
if(!chooser){ chooser = function(key, pres){ return pres } } | ||
return this.map(presences, (key, presence) => { | ||
return chooser(key, presence) | ||
}) | ||
}, | ||
// private | ||
map(obj, func){ | ||
return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key])) | ||
}, | ||
clone(obj){ return JSON.parse(JSON.stringify(obj)) } | ||
} | ||
// Creates a timer that accepts a `timerCalc` function to perform | ||
@@ -712,0 +893,0 @@ // calculated timeout retries, such as exponential backoff. |
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
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
70350
1977