Using Streaming Data from Twitter with Meteor - javascript

So far I have been able to pull down streaming real time data from Twitter. How do I use this data? I am trying to insert it into collection but I am getting this Error:
Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.
I tried wrapping my code with a fiber but it didn't work/or I am not wrapping the right part of the code. Also, I'm not sure if this is the proper way to use streaming data in Meteor.
Posts = new Meteor.Collection('posts');
if (Meteor.isClient) {
Meteor.call("tweets", function(error, results) {
console.log(results); //results.data should be a JSON object
});
}
if (Meteor.isServer) {
Meteor.methods({
tweets: function(){
Twit = new TwitMaker({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
sanFrancisco = [ '-122.75', '36.8', '-121.75', '37.8' ];
stream = Twit.stream('statuses/filter', { locations: sanFrancisco });
stream.on('tweet', function (tweet) {
userName = tweet.user.screen_name;
userTweet = tweet.text;
console.log(userName + " says: " + userTweet);
Posts.insert({post: tweet})
})
}
})
}

Code that mutates the database needs to be run in a fiber, which is what the error is about. Code that runs in a callback from a library other than Meteor is not (necessarily) run in a fiber, so you'll need to wrap the callback function to make sure it gets run in a fiber, or at least the part of it that interacts with the database.
Meteor.bindEnvironment is not currently documented, but it is generally considered the most reliable method of wrapping callbacks. Meteor.bindEnvironment, which the error talks about, is defined here for reference:
https://github.com/meteor/meteor/blob/master/packages/meteor/dynamics_nodejs.js#L63
Something like this is probably the easiest way of making this work:
tweets: function() {
...
// You have to define this wrapped function inside a fiber .
// Meteor.methods always run in a fiber, so we should be good here.
// If you define it inside the callback, it will error out at the first
// line of Meteor.bindEnvironment.
var wrappedInsert = Meteor.bindEnvironment(function(tweet) {
Posts.insert(tweet);
}, "Failed to insert tweet into Posts collection.");
stream.on('tweet', function (tweet) {
var userName = tweet.user.screen_name;
var userTweet = tweet.text;
console.log(userName + " says: " + userTweet);
wrappedInsert(tweet);
});
}

This works for me. Essential is to call Meteor.bindEnvironment from inside the Twit callback.
Meteor.methods({
consumeTwitter: function () {
var Twit = Meteor.npmRequire('twit');
var T = new Twit({
consumer_key: 'xxx', // API key
consumer_secret: 'yyy', // API secret
access_token: 'xxx',
access_token_secret: 'xxx'
});
// search twitter for all tweets containing the word 'banana'
var now = new Date().getTime();
var wrappedInsert = Meteor.bindEnvironment(function(tweet) {
Tweets.insert(tweet);
}, "Failed");
T.get('search/tweets',
{
q: 'banana since:2011-11-11',
count: 4
},
function(err, data, response) {
var statuses = data['statuses'];
for(var i in statuses) {
wrappedInsert(statuses[i]);
}
}
)}
});

I had written a lengthy post about Building Twitter Monitoring Apps with MeteorJS from Scratch, including the Meteor.bindEnvironment part, extract as below.
var Twit = Meteor.npmRequire(‘twit’);
var conf = JSON.parse(Assets.getText(‘twitter.json’));
var T = new Twit({
consumer_key: conf.consumer.key,
consumer_secret: conf.consumer.secret,
access_token: conf.access_token.key,
access_token_secret: conf.access_token.secret
//
// filter the twitter public stream by the word ‘iwatch’.
//
var stream = T.stream(‘statuses/filter’, { track: conf.keyword })
stream.on(‘tweet’, Meteor.bindEnvironment(function (tweet) {
console.log(tweet);
Tweets.insert(tweet);
}))
There are only two functions added:
Meteor.bindEnvironment()
This function helps us to bind a function to the current value of all the environment variables.
Have fun!

Related

How do I pass a JSON string to an AWS Lambda function from webpage Javascript without any API calls?

I am attempting to send data entered into various fields of an HTML form on a webpage as a JSON string to AWS Lambda so that Lambda can enter that into a DynamoDB table. As this is for a class project, I've chosen to forego using a Gateway API, I just want to raw call the Lambda function from inside the webpage javascript and pass the JSON in as a parameter to the Lambda function. I have the webpage successfully calling the Lambda function, which I have hard coded to enter a predefined entry into the Dynamo table. I also have the web js making a JSON string from the form. My goal is to send the JSON string as a parameter to the Lambda function when I invoke it, but I'm not sure how I would go about that, as this is my first time working with AWS. I know I have to do something with the payload parameter, but I can't find a clear example as to what. I've made sure I have the proper credentials and SDK imports in the HTML. Below is my code:
Webpage JS:
var lambda = new AWS.Lambda();
function makeJSON(){
var userID = "";
var name = document.forms["characterForm"]["characterName"].value;
//alert(name);
//alert(typeof name);
var race = document.forms["characterForm"]["race"].value;
var playerClass = document.forms["characterForm"]["class"].value;
var strength = document.forms["characterForm"]["strength"].value;
var dexterity = document.forms["characterForm"]["dexterity"].value;
var constitution = document.forms["characterForm"]["constitution"].value;
var intelligence = document.forms["characterForm"]["intelligence"].value;
var wisdom = document.forms["characterForm"]["wisdom"].value;
var charisma = document.forms["characterForm"]["charisma"].value;
//alert(name + race + playerClass + strength, dexterity, constitution, intelligence, wisdom, charisma);
characterSheetObj = {userID: userID, name: name, race: race, class: playerClass, strength: strength, dexterity: dexterity, constitution: constitution, intelligence: intelligence, wisdom: wisdom, charisma: charisma}
characterSheetJSON = JSON.stringify(characterSheetObj);
var myParams = {
FunctionName : 'addCharacterSheet',
InvocationType : 'RequestResponse',
LogType : 'None',
//Payload : {"userID": userID, "name": name, "race": race, "class": playerClass, "strength": strength, "dexterity": dexterity, "constitution": constitution, "intelligence": intelligence, "wisdom": wisdom, "charisma" : charisma}
}
lambda.invoke(myParams, function(err, data){
//if it errors, prompts an error message
if (err) {
alert("Error");
prompt(err);
}
//otherwise puts up a message that it didnt error. the lambda function presently doesnt do anything
//in the future the lambda function should produce a json file for the JavaScript here to do something with
else {
alert("Invoked Lambda function without erroring!");
}
});
}
Node Lambda Function:
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async (event) => {
// exports.handler = function(e, ctx, callback) {
let scanningParameters = {
TableName : 'characterTable',
Limit:100
};
db.scan(scanningParameters, function(err, data){
if(err){
callback(err,null);
}else{
callback(null,data);
}
});
const params = {
TableName : 'characterTable',
Item: {
name : 'Alan'
}
};
const userID = '12345';
params.Item.userID = userID;
return await db.put(params).promise();
};
//}
I think it has to do with events.body in the Node.js code, but again, I'm not very clear on it, and I can't suss very much out of Amazon's documentation. Any suggestions, tips, or resources to look at would be greatly appreciated!
I don't know if I understand your question correctly but your parameters passed to AWS Lambda is available at event.arguments
Just pass the payload as a JSON string:
var payload_obj = { name: "John", age: 30, city: "New York" };
var payload_json = JSON.stringify(payload_obj);
var myParams = {
FunctionName: 'addCharacterSheet',
InvocationType: 'RequestResponse',
LogType: 'None',
Payload: payload_json
}
lambda.invoke(myParams, function(err, data){
...
});

How to send ajax get request in nightwatch.js

I am working in UI automation testing project i need to send a ajax request to my server but in Nighwatch.js some functions of vanilla javascript and JQuery functions are not acceptable,
so if anyone has any experience to send Ajax get request to server in nightwatch.js environment then please give me some info/suggestions.
After long research i found request.js a node module, I have resolved my issue by installing "request" node module. After installing i am able to perform "GET" and "POST" requests to my servers within Nightwatch environment. I am writing piece of code which working like a charm.
/* jshint expr: true */
module.exports = {
'#tags' : ['book'],
beforeEach : function (client) {
},
after : function (client) {
client.end();
},
wGroup: {
book_url: "https://example.myApi.mycompany.in"
},
userSettings: Array(),
"Get all settings": function (client, done) {
var widget = this.wGroup;
client.getreq( widget.book_url + "/api/store", widget, function (response) {
client.assert.equal(response.statusCode, 200, "201 Created");
var objects = response.body.objects;
client.userSettings = objects;
console.log( 'Found number of settings: ' + client.userSettings.length );
client.end();
});
},
"Remove settings": function (client, done) {
var widget = this.wGroup;
var objects = client.userSettings;
for( i=0; i<objects.length; i++ ) {
var obj = objects[i];
console.log('Removing user settings id ' + obj.id );
client.deletereq( widget.book_url: l + "/api/store" + obj.id, widget, function (resp) {
client.assert.equal(resp.statusCode, 204, "204 Created");
client.end();
});
}
},
};

Trouble chaining HTTP requests in Node/Javascript

I'm attempting to recreate a Python script I wrote in Node/js and I'm having trouble wrapping my head around the asynchronous/callback way of it all.
The script is fairly simple and uses two basic HTTP requests to eBay's API. The first request gets a list of resulting item ids, then the second request gets specific information on each item (description/listing information etc). In Python this was fairly simple and I used a simple loop. It wasn't fast by any means, but it worked.
In javascript, however, I'm struggling to get the same functionality. My code right now is as follows:
var ebay = require('ebay-api');
var params ={};
params.keywords = ["pS4"];
var pages = 2;
var perPage = 2;
ebay.paginateGetRequest({
serviceName: 'FindingService',
opType: 'findItemsAdvanced',
appId: '',
params: params,
pages: pages,
perPage: perPage,
parser: ebay.parseItemsFromResponse
},
function allItemsCallback(error,items){
if(error) throw error;
console.log('FOUND', items.length, 'items from', pages, 'pages');
for (var i=0; i<items.length; i++){
getSingle(items[i].itemId);
}
}
);
function getSingle(id){
console.log("hello");
ebay.ebayApiGetRequest({
'serviceName': 'Shopping',
'opType': 'GetSingleItem',
'appId': '',
params: {
'ItemId': id ,
'includeSelector': 'Description'
}
},
function(error, data) {
if (error) throw error;
console.dir(data); //single item data I want
}
);
}
This is one attempt of many, but I'm recieving "possible EventEmitter memory leak detected" warnings and it eventually breaks with a "Error:Bad 'ack' code undefined errorMessage? null". I'm fairly sure this just has to do with proper utilization of callbacks but I'm unsure how to properly go about it. Any answers or help would be greatly appreciated. I apologize if this isn't a good question, if so please let me know how to correctly go about asking.
Node.js's asynchronous event chain is built on callbacks. Rather than:
getSingle(items[i].itemId);
You'll need to write a callback into that function that executes once the parent function is complete:
getSingle(items[i].itemId, function(err, data) {
// now you can access the data
});
And because ebay.ebayApiGetRequest is a lengthy function, the callback that tells its parent function that it's done must be called after that completes, like so:
ebay.ebayApiGetRequest({
//
},
function(error, data) {
callback(error, data);
}
);
But of course, if the parent function getSingle doesn't support a callback, then it won't go anywhere. So you'll need to support a callback param there as well. Here's the full script, rewritten using the event-driven callback model:
var ebay = require('ebay-api');
var async = require('async');
var params = {};
params.keywords = ["pS4"];
var pages = 2;
var perPage = 2;
ebay.paginateGetRequest({
serviceName: 'FindingService',
opType: 'findItemsAdvanced',
appId: '',
params: params,
pages: pages,
perPage: perPage,
parser: ebay.parseItemsFromResponse
},
function allItemsCallback(error, items) {
if (error) throw error;
console.log('FOUND', items.length, 'items from', pages, 'pages');
async.each(items, function(item, callback) {
getSingle(item.itemId, function(err, data) {
callback(err, data);
});
}, function(err, results) {
// now results is an array of all the data objects
});
}
);
function getSingle(id, callback) {
console.log("hello");
ebay.ebayApiGetRequest({
'serviceName': 'Shopping',
'opType': 'GetSingleItem',
'appId': '',
params: {
'ItemId': id,
'includeSelector': 'Description'
}
},
function(error, data) {
if (error) throw error;
console.dir(data); //single item data I want
callback(error, data);
}
);
}

angular push adds operator that that results in MongoError

I am implementing the tutorial on the mean stack https://www.youtube.com/watch?v=AEE7DY2AYvI
I am adding a delete feature to remove items from the database on a button click
My client side controller has the following 2 functions to add to db and remove
$scope.createMeetup = function() {
var meetup = new Meetup();
meetup.name = $scope.meetupName;
meetup.$save(function (result) {
$scope.meetups.push(result);
$scope.meetupName = '';
});
}
$scope.deleteMeetup = function() {
item = $scope.meetups[0];
console.log("deleting meetup: " + item["name"]);
Meetup.delete(item);
scope.meetups.shift();
}
My server side has the following code
module.exports.create = function (req, res) {
var meetup = new Meetup(req.body);
meetup.save(function (err, result) {
res.json(result);
});
}
module.exports.remove = function(req, res) {
console.log("GOING TO REMOVE!!!");
console.log(req.query);
item = req.query;
Meetup.remove(item, function (err, results) {
console.log("done");
console.log(err);
console.log(results);
});
}
When I run my code and if I delete an already loaded item in the list, it is removed from Mongodb just fine. But if I add an item to the list and I do not refresh the page, it results in an error at my server that appears as
GOING TO REMOVE!!!
{ '$resolved': 'true',
__v: '0',
_id: '54ec04e70398fab504085178',
name: 'j' }
done
{ [MongoError: unknown top level operator: $resolved]
name: 'MongoError',
code: 2,
err: 'unknown top level operator: $resolved' }
null
I if I refresh the page, the it gets deleted fine. But if I added the entry, angular seems to be adding a new variable $resolved. Why is that happening?
Also another question, What is the proper way to call delete? I call it now but I am not able to put a callback. I want a callback which returns and then I shift the list of items. I tried adding a callback but the code never reaches it.
ie I tried the following
/*
Meetup.delete(item, function () {
console.log("In callback!!");
console.log(returnValue);
console.log(responseHeaders);
$scope.meetups.splice(item);
});
*/
/*Meetup.delete(item,
function (returnValue, responseHeaders) {
console.log("In callback!!");
console.log(returnValue);
console.log(responseHeaders);
$scope.meetups.splice(item);
},
function (httpResponse){
// error handling here
console.log("Need to handle errors");
});
*/
I am very new to node and am confused. Any help is very, very appreciated
Looks like it possible to call item.delete instead of Meetup.delete(item). You can call same methods on model instance. It prevent sending angular properties to server.
But better to make a rest API with delete method
DELETE /meetups/:id
and send just a _id
Meetup.remove({id: item._id});

Returning Server Side Data Wrapped in a Fiber to a Client Side Call with MeteorJS

I am trying to get the server side method to return the tweet JSON objects in the browser console. So far my app can pull information from the Twitter API and insert all of that data into a Collection but it won't return data to the call. I've done a bunch of tests with calls and methods to debug this issue and I think the Fiber might change how this call/method works.
http://sf-tweet-locator.meteor.com/
I want to be able to pull the longetude and latitude from each object so that I can place pins of the location of each tweet on a map. I'm not sure if the way that I am doing it is the "best" way but I am open to all suggestions!
if (Meteor.isClient) {
Meteor.call("tweets", function(error, results) {
console.log(results); //results.data should be a JSON object
});
}
Meteor.methods({
Fiber = Npm.require('fibers');
tweets: function(){
Twit = new TwitMaker({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
sanFrancisco = [ '-122.75', '36.8', '-121.75', '37.8' ];
stream = Twit.stream('statuses/filter', { locations: sanFrancisco });
var wrappedInsert = Meteor.bindEnvironment(function(tweet) {
var userName = tweet.user.screen_name;
var userTweet = tweet.text;
console.log(userName + " says: " + userTweet);
Posts.insert(tweet);
return tweet;
}, "Failed to insert tweet into Posts collection.");
stream.on('tweet', function (tweet) {
wrappedInsert(tweet);
return tweet;
});
},
})
You could do it as a publication - that way you're not inserting useless data into your Posts collection,
This example creates a publication, which sends each new tweet to the local-tweets collection on the client.
You could possibly pass a queryId to the subscription/publication, and return that in each tweet document if you need to reference the source query.
if (Meteor.isClient){
var localTweets = new Meteor.Collection('local-tweets');
Meteor.subscribe("tweets", [ '-122.75', '36.8', '-121.75', '37.8' ]);
localTweets.observe({
added: function(doc) { console.log('tweet received!', doc); }
});
}
if (Meteor.isServer){
Meteor.publish("tweets", function(bbox){
var pub = this, stopped = false;
var Twit = new TwitMaker({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...'
});
stream = Twit.stream('statuses/filter', { locations: bbox });
var publishTweet = Meteor.bindEnvironment(function(tweet) {
if (stopped) return;
pub.added("local-tweets", Random.id() /* or some other id*/, tweet);
}, "Failed to tweet.");
stream.on('tweet', publishTweet);
this.ready()
this.onStop(function(){
/* any other cleanup? */
stopped = true;
});
});
}

Categories