public void PresenceTests() { var onJoinDict = new Dictionary <string, Item>(); var onLeaveDict = new Dictionary <string, Item>(); var onSyncCount = 0; OnJoinDelegate onJoin = (string id, Item currentItem, Item newItem) => onJoinDict[id] = newItem; OnLeaveDelegate onLeave = (string id, Item currentItem, Item newItem) => onLeaveDict[id] = newItem; OnSyncDelegate onSync = () => onSyncCount++; var(presence1, channel1) = NewPresence("P1", onJoin, onLeave, onSync); Assert.That(() => GetPresenceIds(presence1), Is.EquivalentTo(new List <string> { "P1" }).After(networkDelay)); var(presence2, channel2) = NewPresence("P2"); Assert.That(onJoinDict.Keys, Is.EquivalentTo(new List <string> { "P1", "P2" }).After(networkDelay)); Assert.That(GetPresenceIds(presence1), Is.EquivalentTo(new List <string> { "P1", "P2" })); Assert.That(GetPresenceIds(presence2), Is.EquivalentTo(new List <string> { "P1", "P2" })); channel2.Leave(); Assert.That(onLeaveDict.Keys, Is.EquivalentTo(new List <string> { "P2" }).After(networkDelay)); Assert.That(GetPresenceIds(presence1), Is.EquivalentTo(new List <string> { "P1" })); // P1:join, P2:join, P3:leave Assert.AreEqual(3, onSyncCount); }
/** * 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. */ private static State SyncDiff( State state, Diff diff, OnJoinDelegate onJoin, OnLeaveDelegate onLeave ) { foreach (var key in diff.Joins.Keys) { var newPresence = diff.Joins[key]; var found = state.TryGetValue(key, out var currentPresence); state[key] = newPresence; if (found) { var joinedRefs = state[key].Metas.Select(m => m["phx_ref"]).ToList(); var curMetas = currentPresence.Metas.Where(m => joinedRefs.IndexOf(m["phx_ref"]) < 0).ToList(); state[key].Metas.InsertRange(0, curMetas); } onJoin?.Invoke(key, currentPresence, newPresence); } foreach (var key in diff.Leaves.Keys) { var leftPresence = diff.Leaves[key]; var found = state.TryGetValue(key, out var currentPresence); if (!found) { continue; } var refsToRemove = leftPresence.Metas.Select(m => m["phx_ref"]).ToList(); var filteredMetas = currentPresence.Metas.Where( m => refsToRemove.IndexOf(m["phx_ref"]) < 0).ToList(); var newPresence = new MetadataContainer { Metas = filteredMetas }; onLeave?.Invoke(key, newPresence, leftPresence); if (newPresence.Metas.Count == 0) { state.Remove(key); } else { state[key] = newPresence; } } return(state); }
// lower-level public static API /** * 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. */ public static State SyncState( State currentState, State newState, OnJoinDelegate onJoin = null, OnLeaveDelegate onLeave = null ) { var joins = new State(); var leaves = new State(); foreach (var key in currentState.Keys.Where(key => !newState.ContainsKey(key))) { leaves[key] = currentState[key]; } foreach (var key in newState.Keys) { var newPresence = newState[key]; var found = currentState.TryGetValue(key, out var currentPresence); if (found) { var newRefs = newPresence.Metas.Select(m => m["phx_ref"]).ToList(); var curRefs = currentPresence.Metas.Select(m => m["phx_ref"]).ToList(); var joinedMetas = newPresence.Metas.Where(m => curRefs.IndexOf(m["phx_ref"]) < 0).ToList(); var leftMetas = currentPresence.Metas.Where(m => !newRefs.Contains(m["phx_ref"])).ToList(); if (joinedMetas.Count > 0) { joins[key] = new MetadataContainer { Metas = joinedMetas }; } if (leftMetas.Count > 0) { leaves[key] = new MetadataContainer { Metas = leftMetas }; } } else { joins[key] = newPresence; } } var diff = new Diff { Joins = joins, Leaves = leaves }; return(SyncDiff(new State(currentState), diff, onJoin, onLeave)); }
public static State SyncState(State currentState, State newState, OnJoinDelegate onJoin, OnLeaveDelegate onLeave) { var state = Clone(currentState); var joins = new State(); var leaves = new State(); foreach (var entry in state) { if (!newState.ContainsKey(entry.Key)) { leaves[entry.Key] = entry.Value; } } foreach (var entry in newState) { if (state.ContainsKey(entry.Key)) { var currentItem = state[entry.Key]; var newRefs = entry.Value["metas"].Select(m => m["phx_ref"]).ToList(); var curRefs = currentItem["metas"].Select(m => m["phx_ref"]).ToList(); var joinedMetas = entry.Value["metas"].Where(m => !curRefs.Contains(m["phx_ref"])).ToList(); var leftMetas = currentItem["metas"].Where(m => !newRefs.Contains(m["phx_ref"])).ToList(); if (joinedMetas.Count() > 0) { joins[entry.Key] = entry.Value; joins[entry.Key]["metas"] = joinedMetas; } if (leftMetas.Count() > 0) { leaves[entry.Key] = Clone(currentItem); leaves[entry.Key]["metas"] = leftMetas; } } else { joins[entry.Key] = entry.Value; } } var diff = new Diff(); diff["joins"] = joins; diff["leaves"] = leaves; return(SyncDiff(state, diff, onJoin, onLeave)); }
public static State SyncDiff(State currentState, Diff diff, OnJoinDelegate onJoin, OnLeaveDelegate onLeave) { var state = Clone(currentState); foreach (var entry in diff["joins"]) { Item currentItem = null; if (state.ContainsKey(entry.Key)) { currentItem = state[entry.Key]; } state[entry.Key] = entry.Value; if (currentItem != null) { var joinedRefs = state[entry.Key]["metas"].Select(m => m["phx_ref"]).ToList(); var curMetas = currentItem["metas"].Where(m => !joinedRefs.Contains(m["phx_ref"])).ToList(); state[entry.Key]["metas"] = curMetas.Concat(state[entry.Key]["metas"]).ToList(); } onJoin?.Invoke(entry.Key, currentItem, entry.Value); } foreach (var entry in diff["leaves"]) { if (!state.ContainsKey(entry.Key)) { continue; } var currentItem = state[entry.Key]; var refsToRemove = entry.Value["metas"].Select(m => m["phx_ref"]).ToList(); currentItem["metas"] = currentItem["metas"].Where(m => !refsToRemove.Contains(m["phx_ref"])).ToList(); onLeave?.Invoke(entry.Key, currentItem, entry.Value); if (currentItem["metas"].Count() == 0) { state.Remove(entry.Key); } } return(state); }
public Task <IAsyncDisposable> OnLeave(OnLeaveDelegate handler) { return(InnerSubscribe(handler, x => x.OnLeave(default(long)))); }
private (Presence, Channel) NewPresence(string id, OnJoinDelegate onJoin = null, OnLeaveDelegate onLeave = null, OnSyncDelegate onSync = null) { var socketFactory = new DotNetWebSocketFactory(); var socket = new Socket(socketFactory, new Socket.Options { channelRejoinInterval = TimeSpan.FromMilliseconds(200), logger = new BasicLogger() }); var url = string.Format("ws://{0}/phoenix_sharp_test", host); socket.Connect(url); Assert.IsTrue(socket.state == Socket.State.Open); var channel = socket.MakeChannel("presence"); var presence = new Presence(channel); if (onJoin != null) { presence.onJoin = onJoin; } if (onLeave != null) { presence.onLeave = onLeave; } if (onSync != null) { presence.onSync = onSync; } Reply?joinOkReply = null; Reply?joinErrorReply = null; channel.Join(new Dictionary <string, object> { { "id", id } }) .Receive(Reply.Status.Ok, r => joinOkReply = r) .Receive(Reply.Status.Error, r => joinErrorReply = r); Assert.That(() => joinOkReply.HasValue, Is.True.After(networkDelay, 10)); return(presence, channel); }