is it possible to block form submit with only nodejs? - javascript

The HTML file where form is in doesn't have any client side javascript code. That I cannot change directly because my task is nodejs and express.
app.post('/',function(request,response){
const htmlCode = fs.readFileSync(__dirname + '/loggain.html');
const loggaInDom = new jsDOM.JSDOM(htmlCode);
const input = request.body.nickname;
try{
if(input.length<3){
throw new Error("nickname must be at least 3 characters");
}
else{
response.cookie('nickName',input);
response.redirect('index.html');
console.log(request.cookies.nickName);
}
}
catch(error){
console.log(error);
}
});
This is part of my nodejs code.
I would like to block form submit when input.length is smaller than 3. Like event.preventDefault() in client javascript code.
Now it throws error in console, which is correct, but browser keeps loading page permanently.
I cannot directly change HTML file but probably can insert client javascript file to HTML with nodejs, but I would like to know if it is possible to do with only nodeJS

Instead of throwing the error you need to send a response to the client with an error status:
if(input.length<3){
response.status(400).send("nickname must be at least 3 characters");
}
Choose the appropriate error code depending on your error: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
You can also send json instead of text if you want some structure that your frontend can use:
if(input.length<3){
response.status(400).json({
type: "validation error",
message: "nickname must be at least 3 characters"
});
}
Centralized error handling
Alternatively you may not want standardize error handling and do the response.status() thing at one location. Express has a way to catch errors but you cannot use the throw keyword. Instead you pass your error to the next function:
app.post('/',function(request, response, next){ // NOTE: next
const htmlCode = fs.readFileSync(__dirname + '/loggain.html');
const loggaInDom = new jsDOM.JSDOM(htmlCode);
const input = request.body.nickname;
try{
if(input.length<3){
throw new Error("nickname must be at least 3 characters");
}
else{
response.cookie('nickName',input);
response.redirect('index.html');
console.log(request.cookies.nickName);
}
}
catch(error){
next(error); // This is how errors are handled in Express
}
});
Now all you need to do is write a default error handler which is a special middleware that accepts four arguments instead of three or two. Make sure this middleware is loaded last after all your routes:
app.use((error, request, response, next) => {
response.status(500).send(error.message);
});
When used with error types you can send different responses to the browser depending on types of error:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError"; // (2)
}
}
Then you can throw:
next(new ValidationError("nickname must be at least 3 characters"))
Which you can handle with:
app.use((error, request, response, next) => {
switch(error.name) {
case "ValidationError":
response.status(400);
break;
default:
response.status(500);
}
response.send(error.message);
});

Related

Why am I getting "Cannot access 'server' before initialization" error in NodeJS?

I am getting the dreaded Cannot access 'server' before initialization error in code that is identical to code that's running in production.
The only things that have changed are my OS version (macOS 10.11->10.14) my NodeJS version (10->12) and my VSCode launch.json, but I cannot see anything in either that would cause an issue. My Node version went from 10 to 12, but in production it went from 8 to 15 without issue. I routinely keep launch.json pretty sparse, and the same error happens using node server in Terminal.
Here is the offending code. The issue occurs because I have shutdown() defined before server and it references server. It's written to add an event-handler and then cause the event. Yes, it could be refactored but it already works. It works, really. In 21 instances spread over 7 servers.
I have tried changing the declaraion/init of server from const to var but that does not fix it. As mentioned, this is code that's running in prod! What's wrong with my environment?
Maybe a better question is: why did this ever work?
'use strict'
const fs = require('fs');
const https = require('https');
const cyp = require('crypto').constants;
const stoppable = require('./common/stoppable.js');
const hu = require('./common/hostutil');
process.on('uncaughtException', err => {
wslog.error(`Uncaught Exception: ${err} ${err.stack}`);
shutdown();
});
process.on('unhandledRejection', (reason, p) => {
wslog.error(`Unhandled Promise Rejection: ${reason} - ${p}`);
});
// 'shutdown' is a known static string sent from node-windows wrapper.js if the service is stopped
process.on('message', m => {
if (m == 'shutdown') {
wslog.info(`${wsconfig.appName} has received shutdown message`);
shutdown();
}
});
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('SIGHUP', shutdown);
function shutdown() {
httpStatus = 503; // Unavailable
wslog.info(`${wsconfig.appName} httpStatus now ${httpStatus} - stopping server...`);
// Error happens on this next line; It should not execute till after server is running already
server.on('close', function () {
wslog.info(`${wsconfig.appName} HTTP server has stopped, now exiting process.`);
process.exit(0)
});
server.stop();
}
// Init and start the web server/listener
var combiCertFile = fs.readFileSync(wsconfig.keyFile, 'utf8');
var certAuthorityFile = fs.readFileSync(wsconfig.caFile, 'utf8');
var serverOptions = {
key: combiCertFile,
cert: combiCertFile,
ca: certAuthorityFile,
passphrase: wsconfig.certPass,
secureOptions: cyp.SSL_OP_NO_TLSv1 | cyp.SSL_OP_NO_TLSv1_1
};
var server = https.createServer(serverOptions, global.app)
.listen(wsconfig.port, function () {
wslog.info(`listening on port ${wsconfig.port}.`);
});
server.on('clientError', (err, socket) => {
if (err.code === 'ECONNRESET' || !socket.writable) { return; }
// ECONNRESET was already logged in socket.on.error. Here, we log others.
wslog.warn(`Client error: ${err} ${err.stack}`);
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
server.on('error', (err)=>{
if ( err.code === 'EADDRINUSE' ) {
wslog.error(`${err.code} FATAL - Another ${wsconfig.appName} or app is using my port! ${wsconfig.port}`);
} else {
wslog.error(`${err.code} FATAL - Server error: ${err.stack}`);
}
shutdown();
})
combiCertFile = null;
certAuthorityFile = null;
// Post-instantiation configuration required (may differ between apps: need an indirect way to plug in app-specific behavior)
stoppable(server, wsconfig.stopTimeout);
// Load all RESTful endpoints
const routes = require('./routes/');
This is a runtime error, which happens only in a very specific situation. But actually this exact error shouldn't happen with var server = ... but only with const server = ... or let server = .... With var server = ... the error message should say "Cannot read properties of undefined"
What happens
You have an error handler for uncaughtException which is calling shutdown() and in shutdown() you are referencing your server. But consider what happens if your code throws an exception before you initialized your server. For instance if your cert or key cannot be read from the disk, cert or key are invalid ... So nothing will be assigned to server, and an exception will be raised.
Then the handler for your uncaught exception will fire and call the shutdown() function, which then tries to access the server, which of course hasn't been initialized yet.
How to fix
Check what the unhandled exception is, that is thrown before your server is initialized and fix it. In your production environment, there is probably no exception, because the configuration and environment is properly set up. But there is at least one issue in your develepment environment, which causes an exception.
Difference between var and const
And the difference between var server = ... and const server = ... is quite a subtle one. For both, the declaration of the variable is hoisted up to the top of their respective scope. In your case it's always global, also for const. But variables declared as var are assigned a value of undefined whereas variables declared as let/const are not initialized at all.
You can easily reproduce this error if you uncomment either error1 or error2 in the following code. But error3 alone won't produce this ReferenceError because bar will already be initialized. You can also replace const bar = with var bar = and you will see, that you get a different error message.
process.on("uncaughtException", err => {
console.log("uncaught exception");
console.log(err);
foo();
});
function foo() {
console.log("foo");
console.log(bar.name);
}
function init() {
// throw new Error("error1");
return { name: "foobar"}
}
// throw new Error("error2");
const bar = init();
//throw new Error("error3");

How to synchronously send mails through nodemailer?

I'm creating an app using nodejs with the nodemailer module to send mails.
The process of my app is to read a list with names and emails, create png diploma files with jimp (based on each name and email) and store and send each one of them through nodemailer to each different mail addresses and after all this is done I want to delete each file but all this in a sync way, because the png diploma and sending the email takes some time:
The syntax of my list is:
const list = [
[name1, email1#mail.com]
[name2, email2#mail.com]
[ ... ]
[namex, emailx#mail.com]
]
Actually I want to wait for each mail to be sent because gmail seems to have a problem to handle sending multiple mails at time, after sending 13 or 15 mails it shows the next err:
error: { Error: Data command failed: 421 4.7.0 Temporary System Problem. Try again later (10). x12sm4645987otk.1 - gsmtp
So, in order to achieve this, I iterate over the list with a classic for loop (a foreach loop does it in an async way and doesn't let me to keep control over the diploma generation), I process each one of the positions of the
//Iterating over mails array
for (let i = 0; i < list.length; i++) {
// Little msg to know what is going on
console.log(`Processing address ${i} out of ${list.length}`)
const element = list[i]
// diplomaData is an object which contains data needed (such as name, course, etc) to create the diploma
diplomaData.name = element[0];
// diplomaDir is the address in which each diploma gets stored, it is returned by the generateDiploma function
diplomaDir = await generator.generateDiploma(diplomaData)
// So once the diploma is generated, I send its address to generateMailContentFunction
// it returns an object which contains text like a greeting, congratulations and of course, the diploma address
mailContent = await mailer.generateMailContent(element, diplomaDir)
// So the only thing pending is to send the email with the correspondent content
await mailer.sendMail(mailContent)
// I've commented this function because even it is declared in an async way it
// await utilities.remove(diplomaDir)
}
This is my sendMail function:
exports.sendMail = async (mailOptions) => {
transporter.sendMail(mailOptions, (err, info) => {
if (err) {
console.log("error: ", err);
} else {
console.log(`Mail sent succesfully!`);
}
});
}
So in few words my problem is that nodemailer seems to launch all the mails at the same time after finishing the loop (I can confirm this because in my console the logs for "Processing address ..." appears before the ones from nodemailer, so I just want to make this process absolutely synchronous, could anybody help me please? :(
Your sendMail function is not asynchronous in nature. It is kicking off an asynchronous function (ie. transporter.sendMail) then immediately returning undefined (as there is no return statement).
exports.sendMail = function(mailOptions){
return new Promise(function (resolve, reject){
transporter.sendMail(mailOptions, (err, info) => {
if (err) {
console.log("error: ", err);
reject(err);
} else {
console.log(`Mail sent successfully!`);
resolve(info);
}
});
});
}
Now when you await mailer.sendMail(mailContent) a promise will be returned & there will actually be something to await. That is, the resolution or rejection of the promise.
Be sure to have a try/catch block enclosing any await operators.

Retrieving data from json and a url

I have a question, what can I do to get specific data back and send to my channel when I type !test?
Normally, when I issue a request to this URL I get the following response:
http://192.168.1.12/JSON?request=getstatus&ref=4030
{"Name":"HomeSeer Devices","Version":"1.0","Devices":[{"ref":4030,"name":"ttt","location":"ttt","location2":"ttt","value":0,"status":"Off","device_type_string":"AC Input Device Unknown Sensor","last_change":"\/Date(1548247933316)\/","relationship":0,"hide_from_view":false,"associated_devices":[],"device_type":{"Device_API":4,"Device_API_Description":"Plug-In API","Device_Type":73,"Device_Type_Description":"Plug-In Type 73","Device_SubType":97,"Device_SubType_Description":"AC[16B5BB2-10]a\u0002y\u0002\u00020\u00020\u00020\u00020\u00020\u00020\u0002n\u00021\u00020"},"device_image":"","UserNote":"","UserAccess":"Any","status_image":"/images/HomeSeer/status/off.gif","voice_command":"tttt","misc":4864}]}
I want the bot to reply with that status every time I execute the !test command.
How can I do that?
Next question: how can I set it to send the request with the value parameter?
http://192.168.1.12/JSON?request=controldevicebyvalue&ref=4030&value=0
I want that if I type !Device 0 it sets value to 0 by issuing that request.
This is how I'm handling commands:
client.on('message', message => {
// If the message is "ping"
if (message.content === '!ping') {
// Send "pong" to the same channel
message.channel.send('pong');
}
});
You can use the request package from npm. You can use the command below to install it:
To use it you first need to require it, then simply put the URL you want to request to: the result will be passed to the callback:
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
In your case, I would do it like this:
client.on('message', message => {
// Detect the command
if (message.content.startsWith('!status')) {
// Issue the request
request('http://192.168.1.12/JSON?request=getstatus&ref=4030', (error, response, body) => {
// If there has been an error, log it
if (error) console.error(error);
// Otherwise, you can reply with the JSON you got back
else message.channel.send("```json\n" + body + "\n```");
});
}
});
If you want to turn that body string into an object, you'll need to JSON.parse() it.
request('http://192.168.1.12/JSON?request=getstatus&ref=4030', (error, response, body) => {
let object = JSON.parse(body);
// Once you have the object you can get all of its properties like you'd normally do
});
Your second problem can be solved in the same way: you just need to set thonURL depending of the argument.
If you still haven't, you'll need to create an argument parser: there are a lot of ways to do that, I'll just show you the easiest one for this example:
client.on('message', message => {
let args = message.content.split(' '), // Get the arguments
command = args.shift(); // Let the first be the command
// If there's no first argument, reply with this message
if (!args[0]) return message.reply("Please enter a value.");
if (command == '!device') {
request('http://192.168.1.12/JSON?request=controldevicebyvalue&ref=4030&value=' + args[0], (error, response, body) => {
// If there has been an error, log it
if (error) console.error(error);
// Otherwise, you can reply with the JSON you got back
else message.channel.send("```json\n" + body + "\n```");
});
}
});
If you need the object from body for some reason, you can parse it as shown above.
Try installing and importing the opn module:
Command Line: $ npm install opn
Then install it into your code: const opn = require('opn')
Then something along the lines of
if (message.content == "!Device 0") {
opn('http://192.168.1.12/JSON?request=controldevicebyvalue&ref=4030&value=0');
}

Not getting error from async method in node

I use the following code
require('./ut/valid').validateFile()
});
in the validate file when I found some duplicate in config I send error
like following
module.exports = {
validateFile: function (req) {
...
if(dup){
console.log("Duplicate found: ");
return new Error("Duplicate found: ");
}
dup is true and the error should be thrown, how should I "catch" it in async method ?
I tried also like following
require('./ut/valid').validateFile(function() {
process.exit(1);
});
what I miss here, I was able to see the console log...
Your approach doens't work because you're doing something asynchronous. A common solution is to use callbacks. In node.js it's common to use a pattern called error first callbacks.
This means you need to pass a callback function to your file validation method and either return an error or your file:
// './utils/validate.js'
module.exports = {
/**
* Validates a file.
*
* #param {Function} next - callback function that either exposes an error or the file in question
*/
file: function (next) {
// ...
if (duplicate) {
console.log('Duplicate found!');
var error = new Error('Duplicate File');
// Perhaps enrich the error Object.
return next(error);
}
// Eveything is ok, return the file.
next(null, file);
}
};
The you can use it like this:
// './app.js'
var validate = require('./utils/validate');
validate.file(function (err, file) {
if (err) {
// Handle error.
process.exit(1);
}
// Everything is ok, use the file.
console.log('file: ', file);
});
I don't have much knowledge in node, but I can tell you, you are returning an error object, which is not an error from JS perspective, it's just an object, to get an error you have to throw an error like:
throw true;
or
throw new Error("Duplicate found: ");
that way it is handled as an error not as a return value

how i catch and insert Meteor.Error alerts From Meteor.Methods in to a client side db?

i just writing a error notification panel in meteor, here i create a client side mongodb, but i cant push Meteor.Error message in to that client side db by throwError function, currently it's shows inside an alert box
collection/signup.js
signupDB = new Meteor.Collection('signup');
Meteor.methods({
signupSubmit : function(postData) {
var signinEmailExist = signinDB.findOne({
email : postData.email
});
if (postData.email && signinEmailExist)
throw new Meteor.Error(422, "exist in signinDB");
var signupEmailExist = signupDB.findOne({
email : postData.email
});
if (postData.email && signupEmailExist)
throw new Meteor.Error(422, "exist in signupDB"); //
var user = _.extend(_.pick(postData, 'email', 'password'), {
insert_time : new Date().getTime() });
var userId = signupDB.insert(user);
return userId;
}
});
client/error/error.js
errorDB = new Meteor.Collection(null);
throwError = function(data) {
errorDB.insert({data: "in throwError", del: "N"})
}
errorDB.insert({data: "in signup", del: "N"}) code is working anywhere inside client folder
here throwError function can't called, but signupSubmit method errors shows in a alert box
is the problem of publication/subscription like thinks (not wrote for signup db) ?
how i catch and insert Meteor.Error alerts From Meteor.Methods in to a client side db ?
is there any other function like throwError to trap Meteor.Methods errors ?
How are you calling the method? You need to do something like:
Meteor.call('signupSubmit', user, function(err) {
errorDB.insert(err);
});
However, you seem to be implementing a custom, insecure authentication system. You shouldn't do this; Meteor has a great, secure built-in Accounts package. All you need to do is (on the client side):
errors = new Meteor.Collection;
Accounts.createUser({
email: email,
password: password
}, function(err) {
errors.insert(err);
});
The Accounts.createUser method automatically returns an error if the username/email is duplicated.

Categories