Stop callback chain and send notification beforeSave method ApostropheCMS - node.js

I'm trying to prevent the user to save a piece if it doesn't achieve some requirements.
Currently I'm doing it like this:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
let error = "";
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
self.apos.notify(req, "Check the compatibility between parent event and subevents", { type: "error" });
error = "Subevents are not compatible with parent event";
}
callback(error);
};
This works but the problem is it shows 2 errors notifications (the default and my custom), 1 because of callback(error) and 1 because of apos.notify.
Any idea how to stop the item of being saved and only show my notification?
Thanks in advance.
UPDATE 1:
As Tom pointed out, my code looks like this now:
// lib/modules/events/public/js/editor-modal.js
apos.define('events-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
apos.notify('A message suitable for this case.', { type: 'error' });
} else {
apos.notify('A generic error message.', { type: 'error' });
}
};
}
});
// lib/modules/events/index.js
var superPushAssets = self.pushAssets;
self.pushAssets = function() {
superPushAssets();
self.pushAsset("script", "editor-modal", { when: "user" });
};
self.beforeSave = async function(req, piece, options, callback) {
return callback("incompatible")
};
For testing purposes I'm just returning the error in beforeSave. The problem is that an exception is being thrown in the browser console and the modal is not properly rendered again. Here's a screenshot about what I'm talking:
I'm trying to debug it and understand what's happening but no clue yet.

In your server-side code:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
return callback('incompatible');
}
return callback(null);
};
And on the browser side:
// in lib/modules/my-pieces-module/public/js/editor-modal.js
apos.define('my-pieces-module-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
return 'A message suitable for this case.';
} else {
return 'A generic error message.';
}
};
}
});
If the error reported by the callback is a string, it is passed to the browser. The browser can then recognize that case and handle it specially. 'my-pieces-module-editor-modal' should be substituted with the name of your pieces module followed by -editor-modal.

Related

"TypeError: Cannot read property 'url' of undefined", and wrong command behavior. - discord.js

I've been working on a "artwall" bot with, basically write to .json file command. I want for the bot to get the first message attachment, get it's url and save it to save.json.
If there's a an attachment present, everything works fine, but if the command was initiated with a url or without any arguments at all, it gives this error:
TypeError: Cannot read property 'url' of undefined
Here's the command code:
const fs = require('fs');
// Export code for command.
module.exports = {
// In name type name of this command to execute it.
name: 'done',
// In description type description.
description: 'N/A',
// In execute() {...} circle brackets type execution parameters.
execute(client, message, args) {
// Type command code here.
const safety = JSON.parse(fs.readFileSync('./assets/save.json', 'utf8'));
const currentdeg = JSON.parse(fs.readFileSync('./assets/currentDEBUG.json', 'utf8'));
// const attache = JSON.parse(fs.readFileSync('./assets/attach.json', 'utf8'));
if(safety == 'no') {
if(currentdeg == 'not claimed') {
message.channel.send('The wall is not claimed yet. Claim it by using `wol claim`');
}
else if(currentdeg == message.author.id) {
const Attachment = (message.attachments).array();
console.log(Attachment);
if (Attachment == []) {
if (!args) {
message.reply('there\'s no image present. Make sure you attached one message or used url.');
return;
}
else {
fs.writeFile('assets/currentDEBUG.json', JSON.stringify('not claimed', null, 2), err => {
// Checking for errors
if (err) throw err;
console.log('Done writing (claim)');
});
fs.writeFile('assets/attach.json', JSON.stringify(args[0], null, 2), err => {
// Checking for errors
if (err) throw err;
console.log('Done writing (attache)');
});
}
}
// stuff
fs.writeFile('assets/currentDEBUG.json', JSON.stringify('not claimed', null, 2), err => {
// Checking for errors
if (err) throw err;
console.log('Done writing (claim)');
});
fs.writeFile('assets/attach.json', JSON.stringify(Attachment[0].url, null, 2), err => {
// Checking for errors
if (err) throw err;
console.log('Done writing (attache)');
});
}
else {
// stuff
message.channel.send('The artwall was claimed by someone else already. Wait for them to finish their work.');
}
}
else {
message.channel.send('The artwall is locked right now. Please wait for the next event!');
}
},
};
Thanks in advance!

RTCMulticonnection room join keeps throwing error : Session-Descriptions not found. Rechecking

I'm using RTCMulticonnection MultiRTC script to capture and stream multiple user cameras.
I guess, If any user refresh page then session keeps alive in background even I've added page unload event
window.onbeforeunload = function() {
rtcMultiConnection.close();
};
my problem is that joining a room after refresh keeps throwing error/warning message Session-Descriptions not found. Rechecking...
Why session description not found? I've checked RTCMulticonnection js and this error is throwing from below function.
function joinSession(session, joinAs) {
if (isString(session)) {
connection.skipOnNewSession = true;
}
console.log(session);
console.log(joinAs);
if (!rtcMultiSession) {
log('Signaling channel is not ready. Connecting...');
// connect with signaling channel
initRTCMultiSession(function() {
log('Signaling channel is connected. Joining the session again...');
setTimeout(function() {
joinSession(session, joinAs);
}, 1000);
});
return;
}
// connection.join('sessionid');
if (isString(session)) {
if (connection.sessionDescriptions[session]) {
session = connection.sessionDescriptions[session];
} else
return setTimeout(function() {
log('Session-Descriptions not found. Rechecking..');
joinSession(session, joinAs);
}, 1000);
}
// connection.join('sessionid', { audio: true });
if (joinAs) {
return captureUserMedia(function() {
session.oneway = true;
joinSession(session);
}, joinAs);
}
if (!session || !session.userid || !session.sessionid) {
error('missing arguments', arguments);
var error = 'Invalid data passed over "connection.join" method.';
connection.onstatechange({
userid: 'browser',
extra: {},
name: 'Unexpected data detected.',
reason: error
});
throw error;
}
if (!connection.dontOverrideSession) {
connection.session = session.session;
}
var extra = connection.extra || session.extra || {};
// todo: need to verify that if-block statement works as expected.
// expectations: if it is oneway streaming; or if it is data-only connection
// then, it shouldn't capture user-media on participant's side.
if (session.oneway || isData(session)) {
rtcMultiSession.joinSession(session, extra);
} else {
captureUserMedia(function() {
rtcMultiSession.joinSession(session, extra);
});
}
}
Upgraded my application with RTCMulticonnection version v3, also used socket.io instead of WebSocket, earlier I was using WebSocket.

What does meen this error: Falsy value for recipient key 'registrationTokens'

I got this error: Falsy value for recipient key 'registrationTokens' while working with gcm push notifications.
Below you can find my code:
Device.find({ user: { $in: users }}, function (err, devices) {
if (err) {
logger.error('500 ' + err)
return res.status(500).json({
code: config.errorCode.status500.code,
message: config.errorCode.status500.message
})
}
var androidRegTokens = []
var iOSReqTokens = []
for (var i = 0; i < devices.length; i++) {
if (devices[i].platform == 'Android') {
androidRegTokens.push(devices[i].deviceToken)
} else {
iOSReqTokens.push(devices[i].deviceToken)
}
}
if (androidRegTokens.length > 0) {
gcmPush('Notification is sent.', androidRegTokens)
}
if (iOSReqTokens.length > 0) {
apnsPush('Notification is sent.', iOSReqTokens)
}
return res.json({ msg: 'Operation succeed.'})
})
Body of the function gcmPush('Notification is sent.', androidRegTokens) is:
this.sender.send(message, { registrationTokens: deviceTokens }, function (err, response) {
if (err) {
console.error(err)
}else {
console.log(response)
}
})
Does anyone know what wrong is with my code? At first push notifications worked perfect, than this error occured each time I call the service.
EDIT:
I have solved this thanx to Luiz Fernando. The problem is the function:
gcmPush('Notification is sent.', androidRegTokens)
I have forgot to add title which is part of the constructor:
function GcmPush (title, message, deviceTokens) {
this.sender = new gcm.Sender(config.gcmSender)
this.sendPushNotification(title, message, deviceTokens)
}
The deviceTokens object is a falsy value, it can be: null, undefined, 0, '', NaN,false.
Maybe it happend because you are filling iOSReqTokens and androidRegTokens nested in an asynchronous operation (it seems Device.find is async). So, the callback of Device.find and the for-loop happens in different times. Probably, the for-loop is happening AFTER the response, so this response will be undefined (and really is).
So, you need to use some async control flow library, such Promise or Async and ensure the correct order of operations.
Also, where you use the pushed registrations?

stack size exceeded when passing objects as metadata in winston

In ,winston when I tried logging by passing a mongoose query result as a metadata argument, winston just spit out like a thousand lines of log before the task quit.
So for a log like this :
tSchool.findById(bus.schoolid,function(err,school){
winston.info('loaded school',school);
});
Here's a small piece of whats get output :
return _next.apply(this, arguments);
}, remove=function wrappedPointCut() {
var args = [].slice.call(arguments);
var lastArg = args.pop();
var fn;
var originalStack = new Error().stack;
var $results;
if (lastArg && typeof lastArg !== 'function') {
args.push(lastArg);
} else {
fn = lastArg;
}
var promise = new Promise.ES6(function(resolve, reject) {
args.push(function(error) {
if (error) {
// gh-2633: since VersionError is very generic, take the
// stack trace of the original save() function call rather
// than the async trace
if (error instanceof VersionError) {
error.stack = originalStack;
}
_this.$__handleReject(error);
reject(error);
return;
}
// There may be multiple results and promise libs other than
// mpromise don't support passing multiple values to `resolve()`
$results = Array.prototype.slice.call(arguments, 1);
resolve.apply(promise, $results);
});
_this[newName].apply(_this, args);
});
if (fn) {
if (_this.constructor.$wrapCallback) {
fn = _this.constructor.$wrapCallback(fn);
}
return promise.then(
function() {
process.nextTick(function() {
fn.apply(null, [null].concat($results));
});
},
function(error) {
process.nextTick(function() {
fn(error);
});
});
}
return promise;
}
So I wanted to know a few things :
Why is passing a mongoose query result, which is supposed to be just a small json object, printing such gibberish?
Will this happen for other objects also - like the err objects in callbacks .etc?
How do I prevent this? Checking each and every log statement to ensure no query results are passed is not very practical.
Thanks in advance.
Update :
Issues #862, #474 and #914 are tracking/related to this problem, but there hasn't been much progress.
This issue has been fixed in pull request #977 of Winston. you can check out the details on the PR page.

Node JS Async.parallel

I'm learning node and would like to optimize the code I did. I tried using Async.parallel to perform the operations and when finished return a json.
I am new to js node and I'm trying to do with async.parallel but I returned [Function] in other code that I'm trying to understand it
getTabletIntRout: function(req, res) {
var reqMAC = req.param('id_tablet');
Tablet.findOne(reqMAC).populate('rout_tablet').exec(function(err, tablet) {
if (err) return next(err);
if (!tablet) return next();
var arrRoutes = tablet.rout_tablet;
if (arrRoutes.length > 0) {
var routesNotRemoved = [];
arrRoutes.forEach(function(route) {
if (route.removed == 'no') {
Rout.findOne(route.id)
.populate('rout_assigned') // Pin
.populate('in_rout') // Tablet
.populate('rout_description_assigned')
.exec(function(err, rout2) {
var arrRout = rout2.rout_assigned;
var routsNotRemoved = [];
if (arrRout.length > 0) {
arrRout.forEach(function(ruta) {
if (ruta.removed == 'no') {
routsNotRemoved.push(ruta);
}
});
}
var arrTablets = rout2.in_rout;
var tabletsNotRemoved = [];
if (arrTablets.length > 0) {
arrTablets.forEach(function(tab) {
if (tab.removed == 'no') {
tabletsNotRemoved.push(tab);
}
});
}
var arrDesc = rout2.rout_description_assigned;
var descripNotRemoved = [];
if (arrDesc.length > 0) {
arrDesc.forEach(function(desc) {
if (desc.removed == 'no') {
descripNotRemoved.push(desc);
}
});
}
rout2.rout_assigned = routsNotRemoved;
rout2.in_rout = tabletsNotRemoved;
rout2.rout_description_assigned = descripNotRemoved;
routesNotRemoved.push(rout2);
});
}
});
setTimeout(function() {
if (routesNotRemoved.length > 0) {
res.json({ info: routesNotRemoved });
} else {
return res.json({"error": "-1", "message": "Todas las rutas asociadas a esa tablet están eliminadas"});
}
}, 2000);
} else {
return res.json({"error": "-2", "message": "No existen rutas asociadas en esa tablet"});
}
}););});}},
I will try to provide some thoughts, hopefully some will make sense in your domain.
Extract a function to make people understand what you're doing in that big function
So instead of
Tablet.findOne(reqMAC).populate('rout_tablet').exec(function(err, tablet) { // ...
You would have
Tablet.findOne(reqMAC).populate('rout_tablet').exec(meaningfulFunctionName);
Don't repeat yourself
So your code becomes shorter and whenever the reader of your code finds a function name he / she already knows what is happening inside that
if (arrRout.length > 0) {
arrRout.forEach(function(ruta) {
if (ruta.removed == 'no') {
routsNotRemoved.push(ruta);
}
});
}
No need to check for empty arrRout as the argument function to arrRout.forEach will simply not run in the case of length being zero.
What you wrote is just a filter function, so why not using filter? Like so
arrRout.filter(function(ruta) {
return ruta.removed == 'no';
});
You can also reuse this, if you extract the anonymous function, for arrTablets and arrDesc.
On the argument: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Don't use a huge if else
Either check for the inverse or return a default or something that makes sense in your domain, but don't have that big chunk of logic, it makes it hard to reason about your code.
Extract more function so that it's easier to use async
You might want to have something like this
async.waterfall([
function(next) {
// here you can put
// Tablet.findOne(reqMAC).populate('rout_tablet').exec
// invoke next with err, tablet
},
function(tablet, next) {
async.each(arrRoutes, function(arrRoute, nextEach) {
// write your code logic here
});
}
], function() {
// decide what to invoke res.json with
});
Remember to extract functions after you're done putting the logic inside the async steps, I didn't do it so it is more clear where to put what.
I hope this makes sense, feel free to ask if you have any doubts.
Next time you post a question please make sure to properly indent it, don't just paste it here.

Resources