I am building a realtime game with Meteor streams. I need to update only one client - send a room ID from server. Users are not logged in so Meteor.userId() is null and therefore I can't use this: http://arunoda.github.io/meteor-streams/communication-patterns.html#streaming_private_page
There is only one URL (homepage) where all things happen. So I don't use any URL parameters for room. Everything is on the server.
I have tried to use Meteor.uuid() instead of Meteor.userId() but uuid is changed after each emit (which is strange).
In socket.io I would do this:
//clients is an array of connected socket ids
var clientIndex = clients.indexOf(socket.id);
io.sockets.socket(clients[clientIndex]).emit('message', 'hi client');
Is there any way to do this in Meteor streams or Meteor itself?
Well, this can be easily done if you decided to use database, but I guess it is not the best option if you have a large number of clients.
So another way to achieve this - without database - is to make a good use of the Meteor's publish/subscribe mechanism. Basically the way it could work is the following:
1. client asks server for a communication token (use Meteor.methods)
2. client subscribes to some (abstract) data set using that token
3. server publishes the required data based on the received token
So you will need to define a method - say getToken - on the server that generates tokens for new users (since you don't want to use accounts). This could be something more or less like this:
var clients = {}
Meteor.methods({
getToken: function () {
var token;
do {
token = Random.id();
} while (clients[token]);
clients[token] = {
dependency: new Deps.Dependency(),
messages: [],
};
return token;
},
});
A new client will need to ask for token and subscribe to the data stream:
Meteor.startup(function () {
Meteor.call('getToken', function (error, myToken) {
// possibly use local storage to save the token for further use
if (!error) {
Meteor.subscribe('messages', myToken);
}
});
});
On the server you will need to define a custom publish method:
Meteor.publish('messages', function (token) {
var self = this;
if (!clients[token]) {
throw new Meteor.Error(403, 'Access deniend.');
}
send(token, 'hello my new client');
var handle = Deps.autorun(function () {
clients[token].dependency.depend();
while (clients[token].messages.length) {
self.added('messages', Random.id(), {
message: clients[token].messages.shift()
});
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
and the send function could defined as follows:
var send = function (token, message) {
if (clients[token]) {
clients[token].messages.push(message);
clients[token].dependency.changed();
}
}
That's a method I would use. Please check if it works for you.
I think using Meteor.onConnection() like a login would enable you to do what you want pretty easily in a publish function.
Something like this:
Messages = new Meteor.Collection( 'messages' );
if ( Meteor.isServer ){
var Connections = new Meteor.Collection( 'connections' );
Meteor.onConnection( function( connection ){
var connectionMongoId = Connections.insert( connection );
//example Message
Message.insert( {connectionId: connection.id, msg: "Welcome"});
//remove users when they disconnect
connection.onClose = function(){
Connections.remove( connectionMongoId );
};
});
Meteor.publish( 'messages', function(){
var self = this;
var connectionId = self.connection.id;
return Messages.find( {connectionId: connectionId});
});
}
if ( Meteor.isClient ){
Meteor.subscribe('messages');
Template.myTemplate.messages = function(){
//show all user messages in template
return Messages.find();
};
}
I have used database backed collections here since they are the default but the database is not necessary. Making Messages a collection makes the reactive publishing easy whenever a new message is inserted.
One way that this is different from streams is that all the messages sent to all clients will end up being kept in server memory as it tries to keeps track of all data sent. If that is really undesirable then you could use a Meteor.method so send data instead and just use publish to notify a user a new message is available so call the method and get it.
Anyway this is how I would start.
Related
I'm attempting to create a program where I use the Steam API. I want to be able to call the method to retrieve a user's info from the client, while keeping the actual code of the method secret from the client, since it contains an API Key. I tried defining the methods as global in a server folder, like this:
key = 'xxxxxxxxxxxxxxxx';
Meteor.steamFunctions = {
getName: function(user){
var userSteamId = user.profile.id;
Meteor.http.get('http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=' + key + '&steamids=' + userSteamId, function(error, resultJSON){
if (error){
return 'Error in Steam API';
} else {
var json = JSON.parse(resultJSON);
return json.personaname;
}
})
},
getPic: function(user){
var userSteamId = user.profile.id;
Meteor.http.get('http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=' + key + '&steamids=' + userSteamId, function(error, resultJSON){
if (error){
return 'Error in Steam API';
} else {
var json = JSON.parse(resultJSON);
return json.avatarfull;
}
})
}
}
I then try to call it like this in a client-side script:
if (Meteor.isClient){
Template.profile.helpers({
'getName': function(){
return Meteor.steamFunctions.getName(Meteor.user());
}
});
}
That, however, throws
Exception in template helper: TypeError: Cannot read property 'getName' of undefined
at Object.Template.profile.helpers.getName
How can I go about keeping the key secret to the user while still accessing the data?
Well, it is not quite as simple as adding a property to the Meteor global. Also, the remote method/call API to do this will involve asynchronous code.
Put the call to the API, with the secret API key, on the server side in code only visible on the server, e.g. the ./server subdirectory. Define a Meteor.method on the server side that can be called with Meteor.call on the client side.
In the server side Meteor method there are method security checks you can make to check for a logged in user or userid, and use this to decide whether to make the calls or ignore the request. You can throw a new Meteor.Error from the server side if a request is improper or there is an error, but these take resources to communicate.
The thing to understand about Meteor is that it has nothing magical to change how Javascript behaves on the browser or the server. The server is ultimately running nodejs. Objects defined on the server do not magically migrate to the client, or vice versa. If an object is defined on both, it is actually two separate pieces of code.
Therefore, in the client code, the Meteor.call to call the server-side code from the browser... is actually using an existing websocket or ajax API that is asynchronous in nature. This means that you will need to structure client code to provide callback functions on the browser to handle the asynchronously returned results of looking up Name or Pic. A direct return and imperative coding style is not possible.
Typically you'll want to update something on a user's screen as a result of information returned from a lookup. The usual Meteor coding is to have the callback function update a session global variable with Session.set(). Templates can reference these session variables, and through an implied or explicit Tracker.autorun(), the screen can be updated when the API returns the data.
You need to:
Move your steamFunctions into methods which are defined only on the server.
Properly invoke the methods from the client.
Below is some example code based on your original question. Please note this has not been tested and may require some tweaking.
server/methods.js
const KEY = 'xxxxxxxxxxxxxxxx';
const URL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002';
Meteor.methods({
getName() {
const userSteamId = Meteor.user().profile.id;
const params = {
key: KEY,
steamids: userSteamId,
};
try {
var result = HTTP.get(URL, { params });
// Double check this - I have no idea what this API returns. The value
// you want may be nested under result, like result.data or something.
return JSON.parse(result).personaname;
} catch (e) {
// Something bad happened - maybe throw an error.
return false;
}
},
});
Note this method is defined on the server, so we don't expose our KEY to the client. Also note we are using the synchronous version of the HTTP api, so the value can be returned to the client.
client/lib/user.js
Tracker.autorun(function () {
user = Meteor.user();
if (user && user.profile && user.profile.id) {
Meteor.call('getName', (err, name) => {
Session.set('steamName', name);
});
} else {
Session.set('steamName', '');
}
});
When the user logs is or is updated, get the steam name and set a global session variable.
client/templates/profile.js
Template.profile.helpers({
getName: function () {
return Session.get('steamName');
},
});
Read the steamName session variable for use in your template.
I have async function on server side, that invoke from client and get data from some external sources by HTTP.call method. This is temporary data and I don't want put it in Mongo. Is a way to send this data to client except put in Mongo and do Meteor.publish?
This is piece of code:
Meteor.methods({
doRequest: function (partNumber) {
check(partNumber, String);
for (var i = 0; i < sources.length; i++) {
sources[i].params.nr = partNumber;
HTTP.call("POST", sources[i].url, { auth: sources[i].auth, params: sources[i].params }, requestHandler);
}
function requestHandler(err, res) {
if (err) {
throw new Meteor.Error("not-response", "Remote server not responding");
}
// need send array of objects to client
}
}
});
I see two ways to get this done:
Use a client only collection. You can actually publish any data not only collection cursors. Check this out: http://meteorcapture.com/publishing-anything/
[DEPRECATED] Use Meteor streams: http://arunoda.github.io/meteor-streams/
Is there any way to publish once the subscribe request is made and then stop pushing the changes that are made to the collection until the client subscribes again?
I have this scenario:
Server:
Meteor.publish("posts", function () {
return Messages.find(); //Do not push changes to this collection!
});
Client:
Meteor.subscribe("posts");
If you just have to do a one-shot data send to the client, using a method may work:
//Server
Meteor.methods({
getSomePosts : function(limit)
{
check(limit, Number);
return Posts.find({}, {limit : limit}).fetch();
}
});
//Client
Meteor.call('getAllPosts', function(err, result) {
//Do stuff with the result
});
Note that this will be heavy on your database, you may want to use a variable and update it periodically rather than fetching the whole collection each time a client calls the method.
More about limits in the doc!
On the introducing article of DDP, I read that anything can be published, but I've read somewhere ( for example in this Stackoverflow comment Publish arbitrary data and automatically update HTML ) that only Collections can be published.
So where is the truth? If we can publish other things than Collections, I would liek to see an example as I can't find one so far.
From the docs: http://docs.meteor.com/#meteor_publish
Publish functions can return a Collection.Cursor, in which case Meteor will publish that cursor's documents. You can also return an array of Collection.Cursors, in which case Meteor will publish all of the cursors.
So at the moment you can only return a Collection via a cursor (result of a Collection.find()).
To return other data you need to hack into the sockjs stream (the socket library meteor uses to communicate to the server). Be aware this does not guarantee compatibility with future versions of meteor. Sockjs is the library used for meteor to communicate between the server (the wire)
from Publish arbitrary data and automatically update HTML*
client side js
sc = new Meteor._Stream('/sockjs');
sc.on('message', function(payload) {
var msg = JSON.parse(payload);
Session.set('a_random_message', JSON.stringify(msg.data));
});
Template.hello.greeting = function () {
return Session.get('a_random_message');
};
server side js
ss = new Meteor._StreamServer();
ss.register(function (socket) {
var data = {socket: socket.id, connected: new Date()}
var msg = {msg: 'data', data: data};
// Send message to all sockets (which will be set in the Session a_random_message of the client
_.each(ss.all_sockets(), function(socket) {
socket.send(JSON.stringify(msg));
});
});
You can also look at Meteor Streams too. See below.
assume you have added meteor streams via atmosphere - mrt add streams
sc = new Meteor.Stream('hello');
if(Meteor.isServer) {
Meteor.setInterval(function() {
sc.emit('a_random_message', 'Random Message: ' + Random.id());
}, 2000);
Meteor.permissions.read(function() { return true });
}
if(Meteor.isClient) {
sc.on('a_random_message', function(message) {
Session.set('a_random_message', message);
});
Template.hello.greeting = function () {
return Session.get('a_random_message');
};
}
I'm trying to see how can I invoke a js function after the client gets a result from a Meteor method call. The only thing I was able to get is to invoke the function myFunc only on the client that made the actual method call.
Any thoughts how i can invoke the function on all the currently subscribed clients?
here is the code:
function myFunc(error, result) {
alert(result);
}
if (Meteor.is_client) {
Template.container.events = {
'click input' : function () {
Meteor.call('someMethod',myFunc);
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
};
}
if (Meteor.is_server) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Meteor.methods({
someMethod: function() {
//console.log(!this.is_simulation);
return "something";
}
})
Thanks
Currently you can't broadcast a method call to all clients directly. At least as far as I can tell. But a work around would be to create a collection called Alerts and monitor it for changes. Then when you want to send a message to all your users you can change the document in Alerts:
Client:
Alerts = new Meteor.Collection("alerts")
Meteor.autosubscribe(function() {
Alerts.find().observe({
added: function(item){
alert(item.message);
}
});
});
Server:
Alerts = new Meteor.Collection("alerts")
Meteor.publish("alerts", function(){
Alerts.find();
});
Alerts.remove({}); // remove all
Alerts.insert({message: "Some message to show on every client."});
Another option is using Meteor Stream package which purpose is to avoid using a mongodb collection on the server side. It does supports client to clients, server to clients, client to server AND server to servers messaging, including a support for Meteor Cluster
If you want to stay with meteor only using collections, the following code allows you to either broadcast a message from the client to all the clients or a message from the server to all the subscribed clients. Just use this mechanism to then fire a function on the client side once the right message is received. The code is made in such a way that you will never have useless items remaining into the collection.
Messages = new Meteor.Collection("messages");
if (Meteor.isClient) {
Meteor.subscribe("messages");
var query = Messages.find({});
var handle = query.observe({
added: function(document)
{
console.log(document.message);
}
});
// Test the mechanism from the client side
Meteor.call("client talked");
}
if (Meteor.isServer) {
Meteor.startup(function() {
Messages.remove({});
});
Meteor.publish("messages", function()
{
// you might add an optional filter in order to broadcast only the messages you want to the client
return Messages.find();
});
function talk(message)
{
var id = Messages.insert({"message":message});
Messages.remove(id);
}
Meteor.methods(
{
talk: function(message)
{
// you might filter here if the clients can talk using this.userId
talk(message);
}
});
// test the mechanism from the server side
talk("server talked");
}
I like what Zeke said, but for people who uses Meteor 0.5.0+, use Deps.autorun instead of autosubscribe... details at:
https://groups.google.com/forum/#!topic/meteor-core/mTa81RLvhbY
and
http://www.meteor.com/blog/2013/02/14/meteor-055-devshop-code-and-community-contributions
I simple approach to call a JavaScript client-side function would be to add a script tag in your html template that is bound by your collection. Anytime a new item is inserted, this tag would be inserted into the client would run your function. I have a collection call uploads with some properties such as name. The following template triggers drawpoints() client-side function upon receipt of a new item in Uploads collection:
{{#each uploads}}
<tr>
<td>{{name}}</td>
<td>
<div class="alert alert-success">Download Here</div>
</td>
</tr>
<script>drawpoints();</script>
{{/each}}