Is it possible to update a Client template whenever a Server variable changes in a Meteor app?
Based on Meteor docs and searching the web, the only way discussed several times to get the Client side updated whenever Server variable changes is by using a Collection then insert a new document whenever the Server variable change, then through Publish/ Subscribe the client side can get notified, but I was wondering if it there is any other way I can get the Client notified whenever the Server variable changes without the need to use the collections approach. Thanks for your time
even that you have already written about collection to share data between client and server, I suppose, that you do not like the "default" consequence to have a mongodb collection for that.
That is not necessary at all. Lets say, you have a server side state object:
ServerStates = {
version: "1.0",
date: "2015-07-07"
}
This should be send out to all clients (broadcast) who are subscribed
server.js
Meteor.publish("server_states", function() {
// we setup the collection to client just return just on record
this.insert("server_states", "ServerStates", ServerStates);
// signal ready
this.ready();
});
On Client you can use now a minimongo collection to get those information
client.js
ServerStates = new Meteor.Collection("server_states");
Meteor.subscribe("server_states")
If you change a value somewhere on server, you can use my answer from meteor - get all subscriber session handles for a publisher method to publish to all active subscribers.
// publish updated values to all subscribers
function publish_server_state_to_all() {
_.each(Meteor.server.stream_server.open_sockets, function(connection) {
_.each(connection._meteorSession._namedSubs, function(sub) {
if (sub._name == "server_state") {
sub.changed("server_state", "ServerState", ServerState);
}
})
});
}
So if you change a value from ServerState on server side you can do and inform by
ServerState.version = "1.1"
publish_server_state_to_all()
Hope that fits your thoughts
Tom
Related
I'm using Signalr for a realtime notifications for when you get a new message or friend request, and when you view them it changes a "HasBeenViewed" field to true in the database.
The problem is that when Signalr checks for new notifications it doesn't see the changed "HasBeenViewed" field, it thinks it is still false. In the database itself i can confirm that HasBeenViewed has been changed correctly.
The query in Signalr does correctly recognize if a new message/friend request has been added, or if one has been removed. It just doesn't see the updated value for HasBeenViewed.
If I restart IIS then it Signalr finally does see the updated HasBeenViewed value and displays the notifications correctly. However, if a new notification gets sent and viewed, then it is back to not recognizing the viewed status on the new notifications.
Here is my code for the notification check. Using ASP.NET Core. I'm querying from a repository to an MS SQL database.
public void NotificationCheck(string userName)
{
var user = _repo.Query<ApplicationUser>().Where(u => u.UserName == userName).Include(u => u.FreindRequests).Include(u => u.Messages).FirstOrDefault();
var messageCount = user.Messages.Where(m => m.HasBeenViewed == false).Count();
var friendRequests = user.FreindRequests.Where(f => f.HasBeenViewed == false).Count();
Clients.Caller.notificationCount(friendRequests + messageCount);
}
I'm not sure what the problem is. I'm guessing there is some interaction between the query and signalr hub where it is storing it in memory instead of getting the new information every time, but i'm not sure what to do to fix it.
Thanks for any help
Without using SQLDependency triggers, your best option is the manage the message state in the client, and just use the sql database for long term persistence, that way you can get the status of messages when the user logs in, and you have immediate access to state in the client.
I'm using the froatsnook:shopify atmosphere package to create an embedded public app on Shopify. I currently have a couple issues:
1) Getting the access token from the "code" query parameter after a user authenticates. As it mentions in the docs here, I'm supposed to use authenticator.getPermanentAccessToken(code) but what I don't understand is how to get call authenticator if the "code" parameter appears on the callback route (at that point, the authenticator I instantiated on the client pre-auth route is out of scope).
2) The "oAuth" function callback is never called for some reason, even when assigning it to Shopify.onAuth on the server.
3) The difference between post_auth_uri and redirect_uri ?
// I call this during 'onBeforeAction' for iron-router
function beforeAuth (query) {
// is this necessary..?
console.assert(Meteor.isClient);
// get shop name like 'myshop' from 'myshop.shopify.com';
const shop = query.shop.substring(0, query.shop.indexOf('.'));
// use api_key stored in settings
var api_key = Meteor.settings.public.shopify.api_key;
// Prepare to authenticate
var authenticator = new Shopify.PublicAppOAuthAuthenticator({
shop: shop,
api_key: api_key,
keyset: 'default',
embedded_app_sdk: true,
redirect_uri: 'https://45a04f23.ngrok.com/testContent',
//post_auth_uri: ???
// This is doesn't seem to be getting
// called after clicking through the OAuth dialog
onAuth: function(access_token) {
ShopifyCredentials.insert({
shop: shop,
api_key: api_key,
access_token: access_token
});
}
});
// Should i use something different with iron-router?
location.href = authenticator.auth_uri;
// how do i get code in this scope???
// authenticator.getPermanentAccessToken(code);
}
There are a few issues with the way you are trying to set up the authenticator, although it's not really your fault because the way Scenario 3 works in the docs is not an 'out of the box' solution and requires a bunch of custom code, including your own handler (I can provide a gist if you REALLY want to build your own handler, but I suggest using the new server-side onAuth callback instead)
1. Specifying a redirect_uri overrides the package's default redirect_uri handler which is Meteor.absoluteUrl("/__shopify-auth").
So instead, completely remove redirect_uri and put your testContent url in post_auth_uri instead.
2. ShopifyCredentials does not exist in this package. If you want to use it that way, make sure you actually have defined a collection called 'ShopifyCredentials' and insert the record from the server, not the client. Note that you will still need to add a keyset on the server for the API methods to work. If you are using user accounts and would like to permanently store credentials, I suggest saving the credentials to the database and adding the keyset via a server-side onAuth callback.
3. authenticator.getPermanentAccessToken(code) isn't useful unless you are using your own handler. Instead, you can just get access_token from the onAuth callback.
Also keep in mind that if you ever need to reauthenticate from inside the embedded app, you need to use window.top.location.href to break out of the iframe.
If you want a complete, working boilerplate example with user accounts see my gist here:
Authentication with Accounts and Persistent Keysets
If you aren't using accounts, you can use this gist instead, but please note that you really need to come up with some way to check that the current client has permission to request the keyset for a given shop before going to production:
Authentication with Persistent Keysets
I just don't know exactly what I should put on the server side and what on the client side. I understand that the templates goes on the client side. But what about the javascript code? Can you give me an example of some code going on the server side?
You can write all your business logic and complex database operations in your server side code. Typically the code you don't want to serve to the client.
For example.
Method calls
# client-side
Template.post.events({
"click #add-post": function(e) {
var post, post_object;
post = $("#post-message").val().trim();
post_object = {
user_id: Meteor.userId(),
post: post
};
Meteor.call("create_post", post_object,(function(error, response) {
if(error){
..do something
}else{
.. do something else
});
);
}
});
# server-side
Meteor.methods({
create_post: function(post_object) {
return Posts.insert(post_object);
}
});
publish / subscribe
# common
Posts = new Mongo.Collection("posts");
# client-side
Meteor.subscribe("posts");
# server-side
Meteor.publish("posts", function(limit) {
return Posts.find({
user_id: this.userId
});
});
Html, css and Template managers should go into the client-side code. Meteor methods and publishers should go into the server-side code. Read more about structuring the app and data security in official docs.
Here is an example for a collection: Declare, publish and subscribe to it.
Server and client (any directory except private, client, or server, don't use public for that too), declare the collection:
Rocks = new Meteor.Collection('rocks');
Server-side (server directory or in a Meteor.isServer condition) ,publish the collection:
Meteor.publish('allRocks', function()
{
return Rocks.find();
}
Client-side (client directory or in a Meteor.isClient condition), subscribe to the publication:
Meteor.subscribe('allRocks');
You can find a lot of examples in the documentation or in this blog (Discover Meteor).
Edit: For more precision according to OP's question... All code is shared by default (executed by both the server and the client). However, files in the server and private directory will never be sent to the client.
if create a directory named client that goes only to client.
if you create a directory named server that goes only to server.
every thing else you code goes to client and server both. (even if
you use Meteor.isServer check)
you can read more about directory structure here.
You use Meteor.isClient and Meteor.isServer to load the code in the proper place.
Using the folder:
server - goes to the server duh!
client - goes to the client duh!
both - shared code
Everything that is placed outside client or server, is loaded on both places.
When you create Meteor package you've to add manually the files and specify where it should be loaded, example:
api.add_files(['my-packages.js', 'another-file.js'], 'client');
api.add_files(['server/methods.js'], 'server');
On this example althouhg you have a server folder, it doesn't mean that it be placed in the server, in the package scenario.
Something you've code that is going to run on the client and server but some functionalities might only be present at server or client.
Example:
ImageManager = {
uploadImageToAmazonS3 : function(){
if(Meteor.isServer){
//your code goes here
//YOU DON'T WANT TO SEND YOUR AMAZON PRIVATE KEY TO THE CLIENT
//BAD THINGS CAN HAPPEN LIKE A HUGE BILL
var amazonCredentials = Config.amazon.secretKey;
}
else{
throw new Error("You can't call this on the client.");
}
}
}
This a scenario where you can add functions that the client can do like: resizeImage, cropImage, etc and the server can also do this, this is shared code. Send a Private API KEY to the client is out of question but this file will be shared by the server and client.
Documentation: http://docs.meteor.com/#/basic/Meteor-isServer
According to the documentation this doesn't prevent the code from being sent to client, it simply won't run.
With this approach an attack knows how things work at the server and might try an attack vector based on the code that you sent to the him.
The best option here is extend the ImageManager only on the server. On the client this function shouldn't even exist or you can simply add a function throwing an error: "Not available".
How can check, on server side route, if user is logged?
I would add check on 'before', but Metor.user() don't work here.
thanks in advance.
p.s. I have found How to get Meteor.user() to return on the server side?, but not work on iron-router
I'm afraid that this is not possible. I guess that the problem comes from the fact that you're trying to connect to the server with two different protocols - both literally and in logically - so there is no obvious way to relate this two actions.
There is, however, a pretty simple solution that may suit your needs. You'll need to develop a simple system of privileges tokens, or secret keys, or whatever you call them. First, create a server method
var Secrets = new Meteor.Collection("secrets"); // only on server!!!
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
Then, you can now use it on the client to get the secretKey which attach to your AJAX request (or something), either within the HTTP header or in the URL itself. Fear not!
They will all be encrypted if you're using HTTPS.
On the server side you can now retrieve the secretKey from the incoming request and check if it is present in the Secrets collection. You'll know then if the user is granted certain privileges or not.
Also you may want to remove your secret keys from the collection after some time for safety reasons.
If what you're looking to do is to authenticate the Meteor.user making the request, I'm currently doing this within the context of IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user:
###
Verify the request is being made by an actively logged in user
#context: IronRouter.Router.route()
###
authenticate = ->
# Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
# Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
# Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
# Attach the user to the context so they can be accessed at this.user within route
this.user = user
###
Respond to an HTTP request
#context: IronRouter.Router.route()
###
respond = (body, statusCode=200, headers={'Content-Type':'text/json'}) ->
this.response.writeHead statusCode, headers
this.response.write(JSON.stringify(body))
this.response.end()
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus
I have backend meteor server which serves and shares common collections across multiple apps (just sharing mongo db is not enough, realtime updates are needed).
BACKEND
/ \
APP1 APP2
| |
CLIENT CLIENT
I have server-to-server DDP connections running between backend server and app servers.
Atm i'm just re-publishing the collections in app server after subscribing them from backend server.
It all seems working quite well. The only problem tho is that in app server cant query any collections in server side, all the find() responses are empty, in client side (browser) it all works fine tho.
Is it just a coincidence that it works at all or what do you suggest how i should set it up.
Thanks
I realize that this is a pretty old question, but I thought I would share my solution. I had a similar problem as I have two applications (App1 and App2) that will be sharing data with a third application (App3).
I couldn't figure out why the server-side of my App1 could not see the shared collections in App3...even though the client-side of App1 was seeing them. Then it hit me that the server-side of my App1 was acting like a "client" of App3, so needed to subscribe to the publication, too.
I moved my DDP.connection.subscribe() call outside the client folder of App1, so that it would be shared between the client and server of App1. Then, I used a Meteor.setInterval() call to wait for the subscription to be ready on the server side in order to use it. That seemed to do the trick.
Here's a quick example:
in lib/common.js:
Meteor.myRemoteConnection = DDP.connect(url_to_App3);
SharedWidgets = new Meteor.Collection('widgets', Meteor.myRemoteConnection);
Meteor.sharedWidgetsSubscription = Meteor.myRemoteConnection.subscribe('allWidgets');
in server/fixtures.js:
Meteor.startup(function() {
// check once every second to see if the subscription is ready
var subIsReadyInterval = Meteor.setInterval(function () {
if ( Meteor.sharedWidgetsSubscription.ready() ) {
// SharedWidgets should be available now...
console.log('widget count:' + SharedWidgets.find().count);
// clean up the interval...
Meteor.clearInterval(subIsReadyInterval);
}
}, 1000);
});
If there is a better way to set this up, I'd love to know.
I have done this already,
check my app Tapmate or youtap.meteor.com on android and iphone,
I know it will work till 0.6.4 meteor version,
haven't checked if that works on above version,
You have to manually override the default ddp url while connecting,
i.e. go to live-data package in .meteor/packages/live-data/stream_client_socket.js
overwrite this - Meteor._DdpClientStream = function (url) {
url = "ddp+sockjs://ddp--**-youtap.meteor.com/sockjs";
now you won't see things happening locally but it will point to meteor server
also disable reload js from reloading
Thanks