Using meteor sockjs connection to send arbitrary messages to the client - meteor

I would like to send arbitrary messages to an specific DDP client.
For instance calling "write" from the server:
Meteor.server.sessions[ session_id ].socket.write( { data_for_user: "something" } )
But i'm not sure how i'm supposed to "catch" this messages on the client.
I know the following code doesn't work, but i would like to achieve something among this lines:
DDP.client.onmessage( function( data ) {
// i'm guessing i would have to filter my messages
// since all DDP messages fire here?
if( data.data_for_user ) {
// Do i need to tell DDP to don't parse my custom message?
console.log( "got something for you", data )
}
} );

you can catch DDP messages on client like this.
var original = Meteor.connection._livedata_data;
Meteor.connection._livedata_data = function (msg) {
console.log(msg)
return original.call(this, msg);
}

Related

Insert new collection after function runs on server

When I return the geocode from googles API I'm trying to save it into my database. I've been trying to use the code below, to just insert a Test document with no luck. I think it has something to do with meteor being asynchronous. If I run the insert function before the googleMapsClient.geocode function it works fine. Can someone show me what I'm doing wrong.
Meteor.methods({
'myTestFunction'() {
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Test.insert({test: 'test name'});
}
});
}
});
I see now where you got the idea to run the NPM library on the client side, but this is not what you really want here. You should be getting some errors on the server side of your meteor instance when you run the initial piece of code you gave us here. The problem is that the google npm library runs in it's own thread, this prevents us from using Meteor's methods. The easiest thing you could do is wrap the function with Meteor.wrapAsync so it would look something like this.
try {
var wrappedGeocode = Meteor.wrapAsync(googleMapsClient.geocode);
var results = wrappedGeocode({ address : "testAddress" });
console.log("results ", results);
Test.insert({ test : results });
} catch (err) {
throw new Meteor.Error('error code', 'error message');
}
You can find more info by looking at this thread, there are others dealing with the same issue as well
You should run the googleMapsClient.geocode() function on the client side, and the Test.insert() function on the server side (via a method). Try this:
Server side
Meteor.methods({
'insertIntoTest'(json) {
Test.insert({results: json.results});
}
});
Client side
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Meteor.call('insertIntoTest', response.json);
}
});
Meteor Methods should be available on the both the server and client sides. Therefore make sure that your method is accessible by server; via proper importing on /server/main.js or proper folder structuring.
(If a method contains a secret logic run on the server, it should be isolated from the method runs on both server & client, though)

How do you know when a resume login attempt is being made or was completed?

On the client:
Can you tell on page load whether a resume login attempt will be made?
Is there a hook for when the attempt returns? Can I listen for the right DDP message?
EDIT: Looks like Meteor.userId() is defined on page load when a resume login attempt will be made, which takes care of #1.
Here are a couple solutions:
Watch DDP on client
Unfortunately by the time the stream handler is called with the result of the login method, Meteor.connection._methodInvokers has been cleared – hence the search function. It would be nice if there was a different / more efficient way to know resumeMethodId. A few possibilities:
Is it guaranteed to have id "1"?
A hook that is called when Meteor decides to call login
If Meteor.connection._methodInvokers were reactive, I could do an autorun that stops after the id is found.
.
resumeAttemptComplete = (success) ->
console.log 'resumeAttemptComplete', success
resumeMethodId = null
searchForResumeMethodId = ->
for id, invoker of Meteor.connection._methodInvokers
sentMessage = invoker._message
if sentMessage.method is 'login' and sentMessage.params[0].resume?
resumeMethodId = id
if Meteor.isClient
Meteor.connection._stream.on 'message', (messageString) ->
unless resumeMethodId
searchForResumeMethodId()
message = JSON.parse messageString
if message.id is resumeMethodId and message.msg is 'result'
resumeAttemptComplete !message.error
_methodInvokers definition: https://github.com/meteor/meteor/blob/de74f2707ef34d1b9361784ecb4aa57803d34ae8/packages/ddp-client/livedata_connection.js#L79-L83
Server onLogin sends event to client
// server:
// map of connection ids -> publish function contexts
let onResumePublishers = {}
Meteor.publish('onResume', function () {
onResumePublishers[this.connection.id] = this
this.ready()
this.onStop(() => {
delete onResumePublishers[this.connection.id]
})
})
let handleLoginEvent = function({connection, type}, loggedIn) {
if (type === 'resume') {
let publisher = onResumePublishers[connection.id]
if (publisher)
publisher.added('onResume', connection.id, {loggedIn}})
}
}
Accounts.onLogin(function (loginAttempt) {
handleLoginEvent(loginAttempt, true)
})
Accounts.onLoginFailure(function (loginAttempt) {
handleLoginEvent(loginAttempt, false)
})
// client:
let resumeExpires = new Date(localStorage.getItem('Meteor.loginTokenExpires'))
let resumeAttemptBeingMade = resumeExpires && resumeExpires > new Date()
let OnResume = new Mongo.Collection('onResume')
let onResumeSubscription = Meteor.subscribe('onResume')
OnResume.find(Meteor.connection.id).observeChanges(
added(id, {loggedIn}) {
onResumeSubscription.stop()
onResumeAttemptCompleted(loggedIn)
}
})
let onResumeAttemptCompleted = function(success) {
// ...
}
check Meteor.loggingIn() .If you want to know if the user is trying to login or not . docs

Are there 'private' server methods in Meteor?

Is there a way to stop a Client calling a Server Method from the browser console?
I gather from the Unofficial Meteor FAQ that there isn't. I just wanted to check if that's definitely the case - the FAQ isn't really specific. I mean are there no 'private' methods?
In meteor the 'methods' described by Meteor.methods can all be called from the client. In this sense there aren't private methods because the purpose of the RPC call is for the client to make the call.
If you want a 'private' method you could use an ordinary JavaScript method. If you define the method with var, it would only be accessible within the file, and cannot be called from the client.
var yourmethod = function() {
...
}
which is equivalent to:
function yourmethod() {
...
}
Or you can define it so any of your server script can use it:
yourmethod = function() {
....
}
If you mean you want a RPC method call that is accessible only from the javascript code, but not from the javascript console in chrome this isn't possible. This is because the idea behind meteor is all RPCs from the client are not trusted & there is no way to distinguish whether it came from the console or not. You can use meteor user authentication or Collection.allow or Collection.deny methods to prevent any unauthorized changes this way.
I made a private method by checking this.connection to be null.
Ref: http://docs.meteor.com/#/full/method_connection
Ex.
Meteor.methods({
'serverCallOnlyFunc': function() {
if (this.connection === null) {
//do something
} else {
throw(new Meteor.Error(500, 'Permission denied!'));
}
}
});

Signalr (1.0.0-alpha2) Hubs - Can you add client functions after connection has been started?

Using Signalr (1.0.0-alpha2), I want to know if it is possible to add client functions after a connection has been started.
Say I create my connection and grab the proxy. Then I add some Server Fired client functions to the hub to do a few things. Then I start my connection. I then want to add some more Server Fired functions to my hub object. Is this possible?
var myHub= $.connection.myHub;
myHub.SomeClientFunction = function() {
alert("serverside called 'Clients.SomeClientFunction()'");
};
$.connection.hub.start()
.done(function() {
myHub.SomeNewClientFunction = function() {
alert("serverside called 'Clients.SomeNewClientFunction()'");
}
})
This example is not realistic, but I basically want to send my 'myHub' variable to a different object after the hub is started to subscribe to new events that the original code did not care for.
Real Life Example: A dashboard with a number of different hub events (new site visits, chat message, site error). I 'subscribe' after the connection has started and then pass my hub proxy to all of my different UI components to handle their specific 'message types'. Should I create separate Hubs for these or should I be able to add more Server Fired client functions on the fly?
Yes you can. Use the .on method.
Example:
myHub.on('somethingNew', function() {
alert("This was called after the connection started!");
});
If you want to remove it later on use the .off method.
I have the exact same situation. You might want to consider adding another layout of abstraction if you're trying to call it from multiple places.
Here's a preliminary version of what I've come up with (typescript).
I'll start with the usage. SignalRManager is my 'manager' class that abstracts my debuggingHub hub. I have a client method fooChanged that is triggered on the server.
Somewhere in the module that is using SignalR I just call the start method, which is not re-started if already started.
// ensure signalR is started
SignalRManager.start().done(() =>
{
$.connection.debuggingHub.server.init();
});
Your 'module' simply registers its callback through the manager class and whenever the SignalR client method is triggered your handler is called.
// handler for foo changed
SignalRManager.onFooChanged((guid: string) =>
{
if (this.currentSession().guid == guid)
{
alert('changed');
}
});
This is a simple version of SignalRManager that uses jQuery $.Callbacks to pass on the request to as many modules as you have. Of course you could use any mechanism you wanted, but this seems to be the simplest.
module RR
{
export class SignalRManager
{
// the original promise returned when calling hub.Start
static _start: JQueryPromise<any>;
private static _fooChangedCallback = $.Callbacks();
// add callback for 'fooChanged' callback
static onfooChanged(callback: (guid: string) => any)
{
SignalRManager._fooChangedCallback.add(callback);
}
static start(): JQueryPromise<any>
{
if (!SignalRManager._start)
{
// callback for fooChanged
$.connection.debuggingHub.client.fooChanged = (guid: string) =>
{
console.log('foo Changed ' + guid);
SignalRManager._fooChangedCallback.fire.apply(arguments);
};
// start hub and save the promise returned
SignalRManager._start = $.connection.hub.start().done(() =>
{
console.log('Signal R initialized');
});
}
return SignalRManager._start;
}
}
}
Note: there may be extra work involved to handle disconnections or connections lost.

Meteor.http.call gives not allowed by Access-Control-Allow-Origin

When I try to call an external server for JSON queries in Meteor with the Meteor.http.call("GET") method I get the error message "not allowed by Access-Control-Allow-Origin".
How do I allow my meteor app to make HTTP calls to other servers?
Right now I run it on localhost.
The code I run is this:
Meteor.http.call("GET",
"http://api.vasttrafik.se/bin/rest.exe/v1/location.name?authKey=XXXX&format=json&jsonpCallback=processJSON&input=kungsportsplatsen",
function(error, result) {
console.log("test");
}
);
There are other questions similar to this on StackOverflow.
You're restricted by the server you're trying to connect to when you do this from the client side (AJAX).
One way to solve it is if you have access to the external server, you can modify the header file to allow some, or all origins by:
Access-Control-Allow-Origin: *
However, if you place the call on the server side and not provide a callback function, the call will be made synchronously, thus not with AJAX, and it should succeed.
Here's
Meteor.methods({checkTwitter: function (userId) {
this.unblock();
var result = Meteor.http.call("GET", "http://api.twitter.com/xyz", {params: {user: userId}});
if (result.statusCode === 200) return true
return false;
}});

Resources