Navigation properties do not get refreshed - javascript

I have a Home entity that I send from my server down to my clients.
public Home {
public HomeId {get;set;}
public String Address {get; set;}
public ICollection<Room> Rooms{ get; set;}
}
All data management on the client side is handled by BreezeJs.
My web application is multiuser. Whenever a user updates a Home entity, the server notifies all interested clients using SignalR.
When a client is notified, it runs the following query to refresh its cache:
function refresh(homeId) {
var query = entityQuery.from('Homes')
.withParameters({ homeId: homeId });
return manager.executeQuery(query)
.fail(queryfailed);
function queryfailed(data) {
// error
}
}
The server side controller:
[HttpGet]
public Home Homes(int homeId)
{
var home = _context.Context.Homes
.Include(t => t.Rooms)
.FirstOrDefault(t => t.HomeId == homeId);
return home;
}
Problem
When a Home entity is refreshed, all properties get the new values from the server but not the navigation properties. If a user adds/removes a Room to/from a given House then the related Rooms entities in cache don't get refreshed although the json data looks fine and contains the added/removed Room entities.
Questions
Is there a simple way to tell Breeze to update the Home and all its navigation properties?
Ideally, I would like to do this without clearing the cache as explained here to avoid some flickering on my UI.
Update I
I tried expanding my query to include Rooms by
var query = entityQuery.from('Homes')
.withParameters({ homeId: homeId })
.expand("Rooms");
Still the same issue, the json data contains the changes made by other clients but are not reflected on the local entities
Update II
I managed to get the changes to propagate to clients using expand. However, changes get propagated only when Rooms are added but not when they are removed.

Just to clarify, my guess is that you are dealing with "rooms' being added and removed from one "house" by one client and these updates not being correctly refreshed on another client with the same "house".
If this is the case, the reason is that the breeze client is able to determine which "rooms" have been added to a house by the other client but it can't determine which rooms have been moved or removed unless you also requery the rooms.
To clarify, imagine if Room 1 is moved from House 1 to House 2 by Client A. Client B still has House 1 containing Room 1. When Client B requeries House 1 (with an Include) it will return no rooms, but breeze will NOT remove Room1 from House 1 because it doesn't know where it moved to. If you were to requery either Room1 or House 2 with an "Rooms" include, then breeze would have sufficient information to "move" the room.
So one approach you could follow would be to simply detach "all" rooms associated with a specific house before "refreshing" the house. You would "lose" any houses that had been moved to another house but these would presumably be refetched by a refresh of that house.
Not sure how clear this is, but...

Related

Express-session: first user session overwritten by second user session

This is a get request where the user is passed to the view
app.get('/blog/:id', function(req, res) {
// Define which page
res.render('blog', {
user: req.user,
});
});
This is a textarea where all of the users on this page should have their names stored.
<textarea readonly id="usersInRoom" placeholder="<%= user.username %>"></textarea>
This successfully sets the correct username as the placeholder. If a second user renders the same view and visits this page at the same time as the first user, the second user's username will be the only placeholder. How would one store the first username so that when the second user opens the page, the placeholder can be a concatenation of the first username + the second username? The data stored in any embedded javascript arrays etc. by the first user are not available to the second user.
If you are using SignalR, then you could broadcast the current list of users to each connection and update the view like in the sample chat application.
If you are using regular ajax server polling, set a client method on an interval that gets a list of current page users from a database store. Because of http's lack of state, you would have to 'retire' users from the list if they haven't posted in awhile.
Client code:
setInterval(checkServer,5000);
function checkServer(){
//some ajax call to the server here
//that returns your user list
...
success:function(result){
$('#usersInRoom').val(result);
}
}
Server code (assuming you are saving all your current page users in a table
with a field called LastModified):
CurrentPageUser class (store this in your DbContext as the CurrentPageUsers table):
public class CurrentPageUser{
public int Id {get;set;}
public DateTime LastModified {get;set;}
public string Name {get;set;}
}
Controller method:
public string GetCurrentUsers(int selfid){
using (var db=new DbContext()){
var now=DateTime.Now;
//update your requesting user so other users can see him/her
db.CurrentPageUsers.Find(selfid).LastModified=now;
db.SaveChanges();
DateTime timelimit = now.AddMinutes-5);
return string.Join(", ",
db.CurrentPageUsers.Where(u=>
u.LastModified<timelimit).Select(u=>u.Name).ToList());
}
}
Note that the SignalR implementation has much better performance and is less clumsy to implement on the server side with no need for a database table to store the current page users. It was made for this type of scenario.
I found a solution. I did in fact have express-sessions persisting my login sessions. The first session was overwritten by a second user who logged in while the first user was online, which replaced the first user's credentials with the second users, and now I had user 2 online in two different browsers.
Although I tried this on different browsers, from different devices, the problem was that the node app was hosted locally at localhost. When I uploaded the app to heroku and accessed it from there, the user session was not overwritten.

Looking for a Cloud Code ( javascript ) expert, dealing with Installations and channels

So my biggest problem is that I never wrote a single line of javascript before but as I developing my application it has become more and more clear I have to use it in Cloud Code.
I have a parse class named Groups, in this I store groups with the following data:
Name, Description, Users, Requests.
If a user creates a group from their device it will be here with the given name, description and the user pointer in it, and I'm using the new objectId with a + "C" char (sometimes objectId starts with a number but its not valid for a channal) to subscripte that user ( installation ) to a channel, so if I query the groups, I get the objectId and I can send every member of the group a push notification.
My first problem is here, that if i store the channel to a installation object in parse with the following code:
groups = new Groups();
groups.setName(groupName.getText().toString());
groups.put("members", ParseUser.getCurrentUser());
groups.setDesc(description.getText().toString());
groups.saveEventually(new SaveCallback() {
#Override
public void done(ParseException e) {
//Subscribe to push
ParsePush.subscribeInBackground("C" + groups.getObjectId(), new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
Log.d("com.parse.push",
"successfully subscribed to the broadcast channel.");
} else {
Log.e("com.parse.push", "failed to subscribe for push", e);
}
}
});
If the user has multiple devices then he wont get the push notifications on every device just on that where he created the group. I searched for it and I am sure I have to use a Parse Cloud function for the subscribing process and with it all the Installations of a specific user will subscribe to the same channels.
Here is my first problem. Can anyone help with it or just give me some reference to solve this (I read all the Cloud Documentation)
Notice:
After the registration I add the user pointer to the Installation like this:
ParseInstallation.getCurrentInstallation().put("user", ParseUser.getCurrentUser());
My second problem:
As I find others have problem with it too and there was some solutions but my problem is a bit more complex. So as I noticed, if I send a client side notification from a device, it will be delivered to the selected channels, it working fine, but if I delete the application then reinstall it, and I'm sending client side notification, it wont delete the multiple installation objects for the device and I will get the notification twice
(I know that if I send notification from parse dashboard it is doing the delete job).
So I need a cloud function that is checking that if there is an other installation with the same user and androidId ( added at install, get it from Android Security ) , if there, then delete it, check if there is an other installation with the same user but not with the same andoidId, copy that installation's channels to my new one and then save the new installation.
Same as above, reference, example code, anything.
I think it will be usefull for others too and hope that someone can help me.
(P.S.: If you need more information please comment, I will always answer in 24 hours)
Whenever the app opens, make sure that the device subscribes to the channel:
"user_" + ParseUser.getCurrentUser().getObjectId()
Then you will send the push notifications to all the devices that the user is signed into.
First problem solution:
I am showing the Groups in a listView, and when a new group is added, the list have to refresh itself, it do it with this method:
public static void updateData() {
ParseQuery<Groups> query = ParseQuery.getQuery("Groups");
query.whereEqualTo("members", ParseUser.getCurrentUser());
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_THEN_NETWORK);
query.findInBackground(new FindCallback<Groups>() {
#Override
public void done(List<Groups> parseObjects, ParseException e) {
if(e != null) {
return;
}
for( int i = 0; i < parseObjects.size(); i++){
Groups iter = parseObjects.get(i);
ParsePush.subscribeInBackground("C" + iter.getObjectId());
}
groupsAdapter.clear();
groupsAdapter.addAll(parseObjects);
}
});
}
With this method (added to onResume) every time I resume to the application it refreshes the list and the device subscribing to all the channels created from the objectId's of the groups where he is a member.
I know it is not a well solution but for now it is fine for me.
And still looking for a better one and for my second question.

Pusher one to one chat structure

I'm a bit confused on the presence-channels in Pusher's platform, as I'm building a chat application from scratch. Now, I know some of you guys have seen tons of "realtime chat app" topics around, but, I'm looking for a peer-to-peer chat and not the site-wide global thingy. More like a facebook chat, where you can go one-to-one.
Now, I've seen an example in PubNub's demos (named Babel) but, that thing is far from what I'm looking for because I've checked the requests in the console and even if it's not shown, the sent messages between other users are shown in my network request logs too because it's being filtered in JS and not server-side and thats not something I want for sure.
So, coming back to the subject,
I'm aware of the channel / private-channel / presence channel functionality, and I decided to do this:
When opening the app, every user subcribes to his private-user_id channel ( creates, if it doesn't exist already ).
At the same time ( while opening the app ) user1 subscribes to a presence-global channel where others keep track if friends are online.
When others want to send him a message, e.g. user2 to user1, he subscribes to private-1 thereafter javascript will process the events.
Now, I know something's wrong with this because.. if user3 would send a message to user1 he'd subscribe to private-user1 so I guess he'll see the events that user2 is triggering when sending messages to user1 too, right ? Or did I get this wrong ?
I've read in their docs that presence channel is actually a private channel extension, so I'm thinking now.. why using private channels anymore, and then, how can I notify all my friends I'm online.
But then, something else comes up in their docs , telling me that channels provide two important things (among others), from which, first is a way of filtering data and second is a way of controlling access.
How am I supposed to "filter data" since there's no link in their docs, or better, what do you have in mind for a one-to-one chat. I'm sorry if I got all their docs wrong, I had a look on their sample applications but none of them are using the one-to-one technique which I'm looking for.
I am new to Pusher and socket connections etc, but I've learned how to authenticate, how to create , detect and process the events in the channel, and I can create a simple global chat with online members, but, when it comes to private-channels I'm quite confused on how to create separate channels for two users.
Thanks in advance !
The purpose of private channels is to restrict who can subscribe to that channel. So, you can either:
Use it to ensure only a users friends can subscribe to updates
Use it for notifications only for that user
In one-to-one chat I'd suggest you choose the latter (No.2).
With this in mind I'd set out achieving one-to-one chat as follows:
The Forum
When users join the chat application they all subscribe to two channels:
private-notifications-<user_id> where user_id is their unique user ID e.g. leggetter in my case. This channel is utilised for user-specific notifications.
presence-forum for all users in that forum. The question called this presence-global.
This is achieved as follows:
var notifications = pusher.subscribe( 'private-notifications-user_one' );
var forum = pusher.subscribe( 'presence-forum' );
Upon subscription to each channel the channel authentication process will take place.
Within the forum you could have a general public chat on the presence-forum/presence-global presence channel by sending and receiving messages.
Starting one-to-one chat
When one user (user_one) wants to have a private chat with another user (user_two) you obviously need something in the UI to trigger this. Say user_one clicks on something next to user_two that indicates they want a one-to-one chat. When this happens a request should be made to the server (the authority) to indicate that user_one wants to initiate the private chat with user_two†.
Note: † if you chose a channel naming convention for one-to-one chat the private channel authentication could actually be used as the private one-to-one chat initiation
When the server receives this request it can generate a unique private channel name for this one-to-one chat. A really simple way of doing this is by concatenating the user IDs e.g. private-chat-<initiating_user>-<receiving_user> (there are other considerations e.g. maybe you want to ensure the channel name is always the same between the two users). In our simple scenario the channel name would be private-chat-user_one-user_two.
The server can then trigger a one-to-one-chat-request event on the private notification channel for each user delivering the one-to-one private chat channel name in the payload.
// Trigger event on both user channels with one call
var channels = [ 'private-notifications-user_one', 'private-notifications-user_two' ];
// Additional event data could also be sent
// e.g. more info on the initiating user
var eventData = {
'channel_name': 'private-chat-user_one-user_two',
'initiated_by': 'user_one'
'chat_with' : 'user_two'
};
pusher.trigger( channels, 'one-to-one-chat-request', eventData );
When user_one receives the one-to-one-chat-request they will subscribe to the eventData.channel_name channel and the auth process will take place for that channel.
// A lookup of private chats
// where the key is the user ID of the current user is chatting with
var privateChats = {};
notifications.bind( 'one-to-one-chat-request', function( data ) {
// MY_USER_ID would need to be stored somewhere
// and in this case the value would be 'user_one'.
// expectingChatWith should make sure user_one is waiting for
// a private chat response with the given user
if( data.initiated_by === MY_USER_ID &&
expectingChatWith( data.chat_with ) ) {
startPrivateChat( data.chat_with, data.channel_name );
}
} );
function startPrivateChat( withUserId, channelName ) {
privateChats[ withUserId ] = pusher.subscribe( channelName );
}
When user_two receives the one-to-one-chat-request the user will need to be notified about the request and either accept or decline it. If the user accepts then the client-side code simply subscribes to the channel. If the user declines then a request should be sent to the server and an event triggered on private-notifications-user_one telling them their one-to-one chat request was declined. This will allow user_one to unsubscribe from the private chat channel.
var privateChats = {};
notifications.bind( 'one-to-one-chat-request', function( data ) {
if( ... ) { ... }
// has somebody request to chat with this user?
else if( data.chatWith === MY_USER_ID ) {
// Prompt the user
// Note: more user info required
displayChatPrompt( data );
}
} );
// callback when the user accepts the chat request
function accepted( chatUserId, channelName ) {
startPrivateChat( chatUserId, channelName );
}
// the user doesn't want to chat
function declined( chatUserId ) {
// send info to the server indicating declined request
}
Private one-to-one chat success
With both user_one and user_two subscribed to private-chat-user_one-user_two they can trigger events on the channel and participate in their private one-to-one chat.

Checking whether user subscribed to certain channel in socket.io

Is it possible to check whether a user has subscribed to a certain channel in socket.io?
Let´s say I have a channel called news. User subscribed to that channel on client-side. But the site´s data is dynamic, therefore the news-tab of the site might not be open at any time. I do not want to create the content for the news-tab if the news tab is not open on the client-side. I know that the news-tab is not open, when the user has not subscribed to the news channel.
Is there a way to check that?
You can use io.sockets.clients('news') and this will return the socket id's of all clients. If you know the client socket.id, you could also call io.sockets.manager.roomClients[socket.id] (room name will have '/' leading character)
This is a sample I use for my admin clients to call to get client counts per room:
socket.on('countClientsInRoom', function (room, callback) {
var count = io.sockets.clients(room).length;
callback(count);
});

Redirect from JavaScript

I'm working on an internal web app and we are using secure query string keys generated server side for some simple security to prevent users from accessing pages they haven't been given access to. The page I am currently working on grabs data via AJAX calls and renders it in a table on the page. Each row has an edit button that will take the user to an edit page with more information, with the id of the row kept in the query string. Since every row id is unique, the key for every edit page will be unique to that row-user combination.
My problem is that I need to be able to get these secure query string keys from the server in some way that allows the JavaScript to redirect the user. I can't move the key generator client side because that opens up the possibility of users generating their own keys for pages they don't have permission to visit. And similarly I can't expose the generator in a web service.
Basically what this boils down to is I am stumped in finding a way to send data from the client to the server in order to generate a secure key and then redirect the user to the new page.
Not exactly sure if I am being 100% clear but I'll edit this as questions come in.
Your question is a little unclear, but PageMethods might work for this:
[WebMethod]
public static string GetSecureID()
{
return "Secure";
}
clientRedirectSecure = function() {
PageMethods.GetSecureID(onSuccess, onFailure);
}
onSuccess = function(result) {
window.location.href = "somepage.aspx?id=" + result;
}
onFailure = function(error) {
alert(error);
}
Here's an article that discusses PageMethods:
http://blogs.microsoft.co.il/blogs/gilf/archive/2008/10/04/asp-net-ajax-pagemethods.aspx

Categories