Is it possible to preserve pointers (object references) when you send data from the server to the client with Meteor? My initial tests say No, but perhaps there is another way to do this.
Here are server-side and client-side scripts:
Server.js
Meteor.methods({
test: function test() {
var object = {
key: ['value']
}
var output = {
object: object
, reference: object.key
}
console.log(output.reference[0])
output.object.key[0] = "changed"
console.log(output.reference[0])
return output
}
})
Server output:
// value
// changed
Client.js
Meteor.call("test", testCallback)
function testCallback(error, data) {
if (!error) {
console.log(data.reference)
data.object.key[0]= "edited"
console.log(data.reference)
}
}
Client output in the console:
// changed
// changed
No, this is not possible, although subscriptions do something similar. If your data is not in a collection, you can still publish it (see this.added and friends in the Meteor docs), and the data will show up in a collection in the client.
Related
Today I try to work on meteor and getting an issue to get data from the server page..
I try to search it on google and meteor side but there is two way to get data publish / subscribe and Method
Have a below code, I didn't know how to write it on meteor server-side and get that data in client-side
function (x)
{
var y =2
var z = y*x
return z;
}
Now I want to call this method in client side
With publish/subscribe you keep track on datas collected.
https://docs.meteor.com/api/pubsub.html
According to the documentation:
Methods are remote functions that Meteor clients can invoke with
Meteor.call.
So, to answer the last question: to call your function from client side, you have to use "Meteor.call('yourMethodName', optionalCallBacks);"
EDIT:
Ok, as suggested in the comments, here an example, with your function.
In the server side, lets say on a file called "methods.js" or somethings:
import { Meteor } from 'meteor/meteor';
Meteor.methods({
myMethod(x) {
const y = 2;
const z = y * x;
return z;
}
});
then in client side, you could call this mehod, like:
Meteor.call('myMethod', x, (error, result) => {
// OPTIONAL CALLBACK RESULT
console.log(error, result);
});
Arguments here, are respectivelly, name of the method, variable named x, callback.
What is the difference between publish / subscribe and method ? where/when we use method and publish / subscribe?
So if you want a logic you don't need pub/sub.
Methods are for logic pub/sub for data handling.
Important note:
If you need to work with data from meteor's collection on the method you need to take into account this if method is executed on the server side it has access to all data (collections).
If the method is executed on the client side it has access only to published data.
On the other hand, according to your example, you don't need any data, so I'll skip it.
I strongly suggest using validated methods:
https://github.com/meteor/validated-method
Now let's go to examples
Imagine you have a method
export const calculate = new ValidatedMethod({
name: 'logic.calculate', // methods are usually named like this
validate: new SimpleSchema({ // use SimpleSchema to autovalidate parameters
x: {
type: Number
}
}).validator(),
run({ x }) {
const y = 2;
return y*x;
}
});
Things to note:
1. The file should be imported on the server somewhere.
2. You need to use validation
Now call it on the client
Meteor.call('logic.calculate', { x }, (error, result) => {
if (error) {
do something
}
console.log(result);
});
Also, you can import the method directly and call it like this:
import { calculate } from '../../api/logic/methods';// use real method path here
calculate.call({ x }, (error, result) => {
if (error) {
do something
}
console.log(result);
});
Note that for validated methods argument is an object
In meteor, we can create a meteor method and can pass data to client side by Meteor.call('methodName', 'param') . But in case of async operation we need to use future. Take a look on below example :
Future = Npm.require('fibers/future');
Meteor.methods({
foo: function() {
const future = new Future();
someAsyncCall(foo, function bar(error, result) {
if (error) future.throw(error);
future.return(result);
});
// Execution is paused until callback arrives
const ret = future.wait(); // Wait on future not Future
return ret;
}
});
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/
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');
};
}
Finding it hard to describe this issue - so please edit if you know more relevant terms.
I'm building a web application which essentially uses Redis (PubSub) + Node.js + Socket.IO as a distribution server.
I have two-way communication working with no issues - but I need to be able to make a request to the server from the client (asynchronously) and deal with the response while still processing other irrelevant responses that might come in before it.
This is what I have so far, but I'm not particularly happy with this approach:
Server
// Lots of other code
redis.psubscribe('*');
redis.on("pmessage", function(pattern, channel, message) {
// broadcast
});
io.on('connection', function(client) {
client.on('message', function(message) {
switch(message.method) {
// call relevant function
}
});
});
function object_exists(object_id) {
// do stuff to check object exists
client.send({method: 'object_exists', value: object_exists});
}
Client
var call = Array();
$(document).ready(function() {
socket.connect();
socket.on("message", function(obj){
console.log(obj);
call[obj.method](obj.value);
});
});
function object_exists(object_id) {
socket.send({method: 'object_exists', value: object_id});
// Set a function to be called when the next server message with the 'object_exists' method is received.
call['object_exists'] = function(value) {
if(value) {
// object does exist
}
}
}
tl;dr: I need to 'ask' the server something and then deal with the response using Socket.IO.
You don't specifically say why you are unhappy with your approach, but it looks to me like you are almost there. I am not really sure what you are trying to do with the call array, so I just took it out for clarity.
Basically, you just need to set up a switch statement to act as a message router on each side of the socket connection and fire off the appropriate methods based in incoming messages. Send enough state with the message itself so you can handle the work without any additional context. In your reworked code, I send the object_id to the server and back again to the client.
///SERVER
// Lots of other code
redis.psubscribe('*');
redis.on("pmessage", function(pattern, channel, message) {
// broadcast
});
io.on('connection', function(client) {
client.on('message', function(message) {
switch(message.method) {
case 'object_exists':
object_exists(message.objectId);
break;
}
});
});
//Takes an id an returns true if the object exists
function object_exists(object_id) {
// do stuff to check object exists
client.send({method: 'object_exists', objectId: object_id, value: object_exists});
}
///CLIENT
$(document).ready(function() {
//setup the message event handler for any messages coming back from the server
//This won't fire right away
socket.on("message", function(message){
switch(message.method) {
case 'object_exists':
object_exists(message.objectId, message.value);
break;
}
});
//When we connect, send the server the message asking if object_exists
socket.on("connect", function() {
socket.send({method: 'object_exists', objectId: object_id});
});
//Initiate the connection
socket.connect();
});
//Get's called with with objectId and a true if it exists, false if it does not
function object_exists(objectId, value) {
if(value) {
// object does exist, do something with objectId
}
else {
// object does not exist
}
}
If you want to see a bunch more code in the same stack doing work similar to what you are trying to accomplish, check out my nodechat.js project.