Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply Glia patches to Phoenix 1.6.5 #3

Open
wants to merge 6 commits into
base: v1.6.5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions assets/js/phoenix/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,29 +161,26 @@
*
* ### Handling individual presence join and leave events
*
* The `presence.onJoin` and `presence.onLeave` callbacks can be used to
* react to individual presences joining and leaving the app. For example:
* The `presence.onChange` callback can be used to react to individual
* presences joining and leaving the app. onChange can only be used when
* deprecated onJoin and onLeave functions are not used.
* For example:
*
* ```javascript
* let presence = new Presence(channel)
*
* // detect if user has joined for the 1st time or from another tab/device
* presence.onJoin((id, current, newPres) => {
* if(!current){
* console.log("user has entered for the first time", newPres)
* presence.onChange((id, oldPresence, newPresence) => {
* if(!oldPresence){
* console.log("user has entered for the first time", newPresence)
* } else if (newPresence.metas.length === 0){
* console.log("user has left from all devices", newPresence)
* } else {
* console.log("user additional presence", newPres)
* console.log("old presence", oldPresence");
* console.log("new presence", newPresence");
* }
* })
*
* // detect if user has left from all tabs/devices, or is still present
* presence.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)
* }
* })
* // receive presence data from server
* presence.onSync(() => {
* displayUsers(presence.list())
Expand Down
9 changes: 4 additions & 5 deletions assets/js/phoenix/longpoll.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,15 @@ export default class LongPoll {
this.onopen()
this.poll()
break
case 403:
this.onerror()
this.close()
break
case 0:
case 500:
this.onerror()
this.closeAndRetry()
break
default: throw new Error(`unhandled poll status ${status}`)
default:
this.onerror({status: status})
this.close()
break
}
})
}
Expand Down
159 changes: 150 additions & 9 deletions assets/js/phoenix/presence.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,62 @@ export default class Presence {
this.channel = channel
this.joinRef = null
this.caller = {
onJoin: function (){ },
onLeave: function (){ },
onChange: null,
onJoin: null,
onLeave: null,
onSync: function (){ }
}

this.channel.on(events.state, newState => {
let {onJoin, onLeave, onSync} = this.caller
let {onChange, onJoin, onLeave, onSync} = this.caller

this.joinRef = this.channel.joinRef()
this.state = Presence.syncState(this.state, newState, onJoin, onLeave)
if (onJoin || onLeave) {
Presence.syncState(this.state, newState, onJoin, onLeave)
} else {
Presence.synchronizeState(this.state, newState, onChange)
}

this.pendingDiffs.forEach(diff => {
this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)
if (onJoin || onLeave) {
Presence.syncDiff(this.state, diff, onJoin, onLeave)
} else {
Presence.synchronizeDiff(this.state, diff, onChange)
}
})
this.pendingDiffs = []
onSync()
})

this.channel.on(events.diff, diff => {
let {onJoin, onLeave, onSync} = this.caller
let {onChange, onJoin, onLeave, onSync} = this.caller

if(this.inPendingSyncState()){
this.pendingDiffs.push(diff)
} else {
this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)
if (onJoin || onLeave) {
Presence.syncDiff(this.state, diff, onJoin, onLeave)
} else {
Presence.synchronizeDiff(this.state, diff, onChange)
}
onSync()
}
})
}

onJoin(callback){ this.caller.onJoin = callback }
// @deprecated Please use onChange instead
onJoin(callback){
console && console.warn && console.warn('onJoin is deprecated, use onChange instead')
this.caller.onJoin = callback
}

onLeave(callback){ this.caller.onLeave = callback }
// @deprecated Please use onChange instead
onLeave(callback){
console && console.warn && console.warn('onLeave is deprecated, use onChange instead')
this.caller.onLeave = callback
}

onChange(callback){ this.caller.onChange = callback }

onSync(callback){ this.caller.onSync = callback }

Expand All @@ -57,13 +80,66 @@ export default class Presence {

// lower-level public static API

/**
* Used to sync the list of presences on the server with the client's state.
* An optional `onChange` callback can be provided to react to changes in the
* client's local presences across disconnects and reconnects with the
* server.
*
* onChange callback will be invoked with three arguments:
* 1. key - presence key (e.g. user-5)
* 2. oldPresence - presence object before the sync
* 3. newPresence - presence object after the sync
*
* **NOTE**: This function mutates the state object that is passed to this
* function as the first argument.
*
* @returns {Presence}
*/
static synchronizeState(state, newState, onChange){
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){
if(joinedMetas.length > 0){
leaves[key] = {metas: leftMetas}
} else {
leaves[key] = newPresence;
leaves[key].metas = leftMetas
}
}
} else {
joins[key] = newPresence
}
})
return this.synchronizeDiff(state, {joins: joins, leaves: leaves}, onChange)
}

/**
* 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.
*
* @returns {Presence}
*
* @deprecated Use synchronizeState function instead
*/
static syncState(currentState, newState, onJoin, onLeave){
let state = this.clone(currentState)
Expand Down Expand Up @@ -97,6 +173,69 @@ export default class Presence {
return this.syncDiff(state, {joins: joins, leaves: leaves}, onJoin, onLeave)
}

/**
*
* Used to sync a diff of presence join and leave events from the server, as
* they happen. Like `syncState`, `syncDiff` accepts optional `onChange`
* callback to react to a user joining or leaving from a device.
*
* onChange callback will be invoked with three arguments:
* 1. key - presence key (e.g. user-5)
* 2. oldPresence - presence object before the sync
* 3. newPresence - presence object after the sync
*
* **NOTE**: This function mutates the state object that is passed to this
* function as the first argument.
*
* @returns {Presence}
*/
static synchronizeDiff(state, {joins, leaves}, onChange){
const changes = {}
this.map(joins, (key, newPresence) => {
changes[key] = {joinedMetas: newPresence.metas, leftMetas: [], update: newPresence}
})
this.map(leaves, (key, leftPresence) => {
if (changes[key]) {
changes[key].leftMetas = leftPresence.metas;
} else {
changes[key] = {joinedMetas: [], leftMetas: leftPresence.metas, update: leftPresence}
}
})

this.map(changes, (key, {joinedMetas, leftMetas, update}) => {
const joinedRefs = joinedMetas.map(m => m.phx_ref)
const refsToRemove = leftMetas.map(m => m.phx_ref)
const oldPresence = state[key];

const newPresence = {metas: oldPresence ? oldPresence.metas : []};
newPresence.metas = newPresence.metas
.filter(m => joinedRefs.indexOf(m.phx_ref) === -1)
.concat(joinedMetas)
.filter(p => refsToRemove.indexOf(p.phx_ref) === -1)

Object.keys(update).forEach(key => {
// metas is already handled above separately
if (key !== "metas") newPresence[key] = update[key];
})

if (newPresence.metas.length === 0) {
// Delete the presence from the state when the metas are empty
delete state[key]
} else {
// Update the old presence with the new presence in one atomic
// operation
state[key] = newPresence;
}

// Only notify onChange when there were any changes. If there were
// changes but the old metas and new betas are still empty then there's
// no reason to notify onChange callback.
if (onChange) onChange(key, oldPresence, newPresence)
});

return state;
}

/**
*
* Used to sync a diff of presence join and leave
Expand All @@ -105,6 +244,8 @@ export default class Presence {
* joining or leaving from a device.
*
* @returns {Presence}
*
* @deprecated Use synchronizeDiff function instead
*/
static syncDiff(state, diff, onJoin, onLeave){
let {joins, leaves} = this.clone(diff)
Expand Down
10 changes: 9 additions & 1 deletion assets/js/phoenix/socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,15 @@ export default class Socket {
}

leaveOpenTopic(topic){
let dupChannel = this.channels.find(c => c.topic === topic && (c.isJoined() || c.isJoining()))
let dupChannel;
for(let i = 0; i < this.channels.length; i++){
let c = this.channels[i];
if (c.topic === topic && (c.isJoined() || c.isJoining())) {
dupChannel = c;
break;
}
}

if(dupChannel){
if(this.hasLogger()) this.log("transport", `leaving duplicate topic "${topic}"`)
dupChannel.leave()
Expand Down
Loading