Refreshing PubSubHubbub subscription for Youtube API in NodeJS - node.js

I managed to subscribe to a Youtube Feed through https://pubsubhubbub.appspot.com/subscribe on a web browser, I've been trying to refresh my subscription to PubSubHubbub through Fetch on NodeJS, the code I used is below
const details = {
'hub.mode': 'subscribe',
'hub.topic': 'https://www.youtube.com/xml/feeds/videos.xml?channel_id=${process.env.ID}',
'hub.callback': process.env.callback,
'hub.secret': process.env.secret
};
const endpoint = 'http://pubsubhubbub.appspot.com/subscribe'
var formBody = [];
for (const property in details) {
const encodedKey = encodeURIComponent(property);
const encodedValue = encodeURIComponent(details[property]);
formBody.push(encodedKey + "=" + encodedValue);
}
formBody = formBody.join("&");
const result = await fetch(endpoint, { method: "POST", body: formBody, 'Content-Type': 'application/x-www-form-urlencoded'})
console.log(result.status, await result.text())
Expected:
Status 204
Actual:
Status 400, content: "Invalid value for hub.mode:"
I expected at least for the content to tell me what the invalid value was, however it ended up being blank, it appears to me that it did not manage to read the POSTed content at all. I'm looking for ways to improve my code so that I don't encounter this problem.

I realised that I was supposed to specify in the content type header that charset=UTF-8. I'm not particularly well-versed in sending requests as of yet so this is definitely an eye-opener for me. After adding it in the server responds with 204 as expected and so I'll close my question.
A workout that I tried while trying to fix this was to import the exec() function from child_process module to run cURL and post the request instead. However I would recommend to stick to one language to make things more readable and using exec() runs commands directly from the shell which may have undesirable effects.

Related

Getting a bad request 400 when trying to upload zipped wkt file to here-maps rest api

We have a problem with the fleet.ls.hereapi.com when uploading a new layer for geofencing.
const myId = 'MYLAYER'; // just a id to check
zip.file('data.wkt', `NAME\tWKT\n${myId}\t${wkt}`);
const content = await zip.generateAsync({ type: 'nodebuffer' });
const formData = new FormData();
formData.append('zipfile', content);
await axios.post(config.HERE_MAPS_UPLOAD_ENDPOINT, formData, {
headers: {
'content-type': 'multipart/form-data',
},
params: {
apiKey: config.HERE_MAPS_REST_API_KEY,
layer_id: myId,
},
});
We get a bad request without a message and do not know what the problem is. The same implementation works in the Frontend (with 'blob' as zip type). Is there a parameter to get a better error message from the api?
We got the instructions how to implement it from this tutorial: https://www.youtube.com/watch?v=Ayw9GcS1V-8 and as I mentioned it works fine in the frontend. Also it works if I write a file in node and upload it via curl. Thank you for any help in advance!
Edit: I'm getting the following issue from the API: 'Multipart should contain exactly one part but contains 0'
I fixed it!
The problem was that the api needed a filename for the form data. This filename can be provided as third parameter as described here.
So I basically changed formData.append('zipfile', content); to formData.append('zipfile', content, zipfile.zip); and it worked.
Hope this will help somebody in the future!

How to use the full request URL in AWS Lambda to execute logic only on certain pages

I have a website running on www.mywebsite.com. The files are hosted in an S3 bucket in combination with cloudFront. Recently, I have added a new part to the site, which is supposed to be only for private access, so I wanted to put some form of protection on there. The rest of the site, however, should remain public. My goal is for the site to be accessible for everyone, but as soon as someone gets to the new part, they should not see any source files, and be prompted for a username/password combination.
The URL of the new part would be for example www.mywebsite.com/private/index.html ,...
I found that an AWS Lambda function (with node.js) is good for this, and it kind of works. I have managed to authenticate everything in the entire website, but I can't figure out how to get it to work on only the pages that contain for example '/private/*' in the full URL name. The lambda function I wrote looks like this:
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
// Configure authentication
const authUser = 'USER';
const authPass = 'PASS';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
The part that doesn't work is the following part:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
If you're using a Lambda#Edge function (appears you are). Then you can view the Request Event structure here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-request
You can see the actual value of the request URI field by using console.log and checking the respective logs in Cloudwatch.
The problem might be this line:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
If you're strictly looking to check if a JavaScript string contains another string in it, you probably want to do this instead:
if (!request.uri.toLowerCase().indexOf("/private/") !== -1) {
Or better yet, using more modern JS:
if (!request.uri.toLowerCase().includes("/private/")) {

Node.js - Why does my HTTP GET Request return a 404 when I know the data is there # the URL I am using

I'm still new enough with Node that HTTP requests trip me up. I have checked all the answers to similar questions but none seem to address my issue.
I have been dealt a hand in the Wild of having to go after JSON files in an API. I then parse those JSON files to separate them out into rows that populate a SQL database. The API has one JSON file with an ID of 'keys.json' that looks like this:
{
"keys":["5sM5YLnnNMN_1540338527220.json","5sM5YLnnNMN_1540389571029.json","6tN6ZMooONO_1540389269289.json"]
}
Each array element in the keys property holds the value of one of the JSON data files in the API.
I am having problems getting either type of file returned to me, but I figure if I can learn what is wrong with the way I am trying to get 'keys.json', I can leverage that knowledge to get the individual JSON data files represented in the keys array.
I am using the npm modules 'request' and 'request-promise-native' as follows:
const request = require('request');
const rp = require('request-promise-native');
My URL is constructed with the following elements, as follows (I have used the ... to keep my client anonymous, but other than that it is a direct copy:
let baseURL = 'http://localhost:3000/Users/doug5solas/sandbox/.../server/.quizzes/'; // this is the development value only
let keysID = 'keys.json';
Clearly the localhost aspect will have to go away when we deploy but I am just testing now.
Here is my HTTP call:
let options = {
method: 'GET',
uri: baseURL + keysID,
headers: {
'User-Agent': 'Request-Promise'
},
json: true // Automatically parses the JSON string in the response
};
rp(options)
.then(function (res) {
jsonKeysList = res.keys;
console.log('Fetched', jsonKeysList);
})
.catch(function (err) {
// API call failed
let errMessage = err.options.uri + ' ' + err.statusCode + ' Not Found';
console.log(errMessage);
return errMessage;
});
Here is my console output:
http://localhost:3000/Users/doug5solas/sandbox/.../server/.quizzes/keys.json 404 Not Found
It is clear to me that the .catch() clause is being taken and not the .then() clause. But I do not know why that is because the data is there at that spot. I know it is because I placed it there manually.
Thanks to #Kevin B for the tip regarding serving of static files. I revamped the logic using express.static and served the file using that capability and everything worked as expected.

Dialogflow actions-on-google: Unable to make Rest api calls before setting the response

Using nodejs I am trying to make a external Rest API call. I have to first make a call and then set the response for the intent.
But when I am trying to do that it expects a Response to be set before i get the response from my API.
Using the V2 version of dialogflow code.
here is the sample
var sText=" ";
console.log("Token in Welcome: "+conv.user.access.token);
var auth = "Bearer "+ conv.user.access.token;
var snQuery = "api/now/table/sys_user?sysparm_query=sys_id=javascript:gs.getUserID()";
var endpointUrl = baseURL +snQuery;
console.log("Endpoint user info: "+endpointUrl);
request.get({
url : endpointUrl,
headers : {
"Authorization" : auth,
"Accept": "application/json",
"Content-Type": "application/json"
}
},
function(error, response, body){
if(error)
{
sText=" Sorry! I found some Issue in performing this action, Please try again after some time. ";
console.log("Entering Error: "+error);
conv.close(sText);
}
else
{
console.log("Acccess token: "+conv.user.access.token);
conv.user.storage.UserDetails = JSON.parse(body);
console.log(conv.user.storage.UserDetails);
console.log("SYS ID_Welcome: "+conv.user.storage.UserDetails.result[0].sys_id);
return asyncTask()
.then(() =>conv.ask(new SimpleResponse({
speech: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?',
text: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?',
})));
}
})
//It is expecting my sample response here. Which would execute before my API call starts.
/*
return asyncTask()
.then(() =>conv.ask(new SimpleResponse({
speech: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?',
text: 'Howdy! tell you fun facts about almost any number, like 42. What do you have in mind?',
})));
*/
I am calling the above snippet in Default Welcome Intent.
const functions = require('firebase-functions');
process.env.DEBUG = 'actions-on-google:*';
const {dialogflow,SimpleResponse} = require('actions-on-google');
const request = require('request');
const app = dialogflow();
app.intent('Default Welcome Intent',getWelcomeMsg);
function getWelcomeMsg(conv)
{
// the above snippet goes here
}
exports.MyFunctionName = functions.https.onRequest(app);
I have tried with both node version 8.11.2 and 6.10 versions
If you are using Firebase Cloud Functions, you need to make sure you are on one of the paid plans. FCF allows network connections only to other Google services if you are on the "Spark" plan. You can upgrade to the "Blaze" plan which has per-use billing, but includes a free tier which is sufficient for most development and light usage.
However, you mention using Node version 8.11.2 and 6.10. Currently, FCF only supports Node version 6.14.
It may also be worthwhile for you to look into using the request-promise-native package instead of request. See the Stack Overflow question at https://stackoverflow.com/a/50441222/1405634

What is the correct syntax for winjs.xhr data

I tried to use winjs.xhr to POST some data to a URL with no success. I got it working by essentially doing the same thing with XMLHttpRequest. This just doesn't feel right, as winjs.xhr, I thought, wraps XMLHttpRequest anyway. Can anyone explain how I do this in winjs.xhr?
Not working winjs.xhr code
Passing everything in as a URL encoded string
var url = "http://localhost/paramecho.php";
var targetUri = "http://localhost/paramecho.php";
var formParams = "username=foo&password=bar" //prefixing with a '?' makes no difference
//ends up with same response passing an object(below) or string (above)
//var formObj = {username: "foo", password: "bar"}
WinJS.xhr({
type: "post",
url: targetUri,
data: formParams
}).then(function (xhr) {
console.log(xhr.responseText);
});
I end up with my receiving PHP file getting none of the parameters, as though I'd sent no data in the first place.
I tried a few things but the code above is the simplest example. If I was to pass an object into the data parameter it behaves the same way (commented out). I've used a FormData object as well as a plain JSON object.
I changed my app manifest to have the correct network capabilities - and the working example below was done in the same app, so I'm confident it's not capability-related.
Working version using XMLHttpRequest
var username = "foo";
var password = "bar";
var request = new XMLHttpRequest();
try {
request.open("POST", "http://localhost/paramecho.php", false);
request.setRequestHeader('Content-type', "application/x-www-form-urlencoded");
request.send("username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password));
console.log(request.responseText);
} catch (e) {
console.log("networkError " + e.message + " " + e.description);
}
And this successfully calls my PHP server-side function, with the parameters I expected.
So, the question is...how do I achieve what I have working in XMLHttpRequest with winjs.xhr? It feels like winjs.xhr the way this is supposed to work (I'm a newbie at Windows 8 app development so I'm happy to be corrected)
You're completely right WiredPrairie. Passing in the header is all that is needed - I think I'd assumed that was the default for a post.
Working version:
WinJS.xhr({
type: "post",
url: targetUri,
data: formParams,
headers: {"Content-type": "application/x-www-form-urlencoded"}
}).then(function (xhr) {
console.log(xhr.responseText);
});

Resources