Passing data from opened window - node.js

I've been stuck on this problem for a day now and was wondering if there's a way I could go about this.
client side
Client side is going to be dynamic since it's an app type plugin.
The client side will open up a popup for oAuth purposes. Let's say its domain is www.google.com
I have the popup written like window.open('www.backend.com/api') in ReactJS.
The client button is something like this:
<button onClick={() => iveBeenClicked()}> sync up </button>
and then it runs this function
const iveBeenClicked = (e) => {
e.preventDefault()
const popupWindow = window.open('www.backend.com/api', "_blank")
}
oAuth and backend with ExpressJS
This is the backend which we'll say is www.backend.com/api
router.use("/api", (req, res) => {
if (req.query.code) {
res.send(res.query.code)
} else {
res.redirect(
`www.ApiRouteThatRedirectsWhenComplete.com`
);
}
});
Well I have the code I want, but how can I send that to the client from a different domain? If there's an alternative to window.open() where a user could press accept, please let me know.
So the big question is, how can client receive data from window.open's popup?
Thank you in advance!
#EDIT
So I finally got it thanks to #vishal-bartakke 's suggestion which I realized I implemented it wrong. I'm going to supply it here just in case it will help anyone that comes across this post.
for the ivebeenclicked() function in client side:
const iveBeenClicked = (e) => {
e.preventDefault()
const popupWindow = window.open('www.backend.com/api', "_blank")
//this is the part that was needed to receive the information of success
window.addEventListener('message', (e) => {
if (e.data) {
//do what you need to do with that key
}
})
}
in my expressjs's file:
router.use(['/api/', '/api/page?'], (req, res) => {
if (req.query.token) {
res.sendFile(path.join(__dirname +'/thatHTMLpage.html'))
} else {
res.redirect(`that route that does the oauth`)
}
})
In that html created upon success, I added this code to send in the data
window.onload = function () {
let urlParams = new URLSearchParams(window.location.search);
window.opener.postMessage(urlParams.get("code"), "*");
};
It is quite a messy setup but it works how I want it.
Thanks!

You can use postMessage (check here) to communicate between windows. In child window you can refer parent window as window.opener for posting message and in parent window you have popupWindow for posting

Related

Is it possible to modify the pug.js-parameter object in an express middleware-callback

Currently I'm on a legacy application using pug.js as view engine in a node.js express-app.
I want to implement a generic way to display feedback messages. I want to be able to display messages (successes, errors), even if the handler does reply with a redirect.
This is what I want:
handlePostRequest(req, res){
// do stuff with the post request
doStuff(req.body);
//This should be done of course somewhere else.
req.session.successes=req.session.successes|[];
//save some success-message for the user
req.session.successes.push("Your post has been saved. Thank you!");
//but reply with a 302
res.redirect(req.headers.referer);
}
//a get request. maybe the handler above redirected here
handleGetRequest(req,res){
// we do NOT get the successes here. Just the 'pure' data.
const renderData=getRenderData();
res.render('fancy-pug-template', renderData);
}
fancyMiddlewareForMessages(req, res, next){
//how to implement getRenderDataByBlackMagic()????
const renderData = getRenderDataByBlackMagic();
//set the messages
renderData.successes = req.session.successes;
//empty saved messages
req.session.successes = [];
next();
}
Obviously, I do not want to polute every handler which actually renders a template with some logic which retrieves the messages and adds them to the parameter object. I would like to move this cross-cutting concern in a middleware callback or something like that.
So, the question is: Can this be achieved? How? I'm fairly new to pug.js, maybe I'm overlooking something obvious.
Ok, I found a way. This is what I did:
const requestStorage = new AsyncLocalStorage<Request>();
function patchRenderFunction(req: Request, res: Response, next: NextFunction) {
const render = res.render;
res.render = function (view: string, options?: any, callback?: (err: Error, html: string) => void) {
const messages = new MessageManager(req);
//merge errorMessages
options.errorMessages = mergeMessageArrays(options.errorMessages, messages.errors);
//same for successMessages
options.successMessages = mergeMessageArrays(options.successMessages, messages.successes);
render.bind(this)(view, options, callback);
};
requestStorage.run(req, () => {
next();
});
}
export function applyAutomaticRenderAttributes(app: Express): void {
app.use(patchRenderFunction);
}
export function successMessage(message: string, req?: Request) {
if (!req) {
req = requestStorage.getStore();
}
if (!req) {
console.error('No request found in async storage. This should not happen. Please report this issue. (successMessage)');
return;
}
new MessageManager(req).addSuccessMessage(message);
}
//export function errorMessage(...) omitted
The MessageManager uses the requests session to store messages. It also filters them in some respect. I'm using the session because the application runs clustered (thank you, pm2). Since the session is stored in the db via express-mysql-session, I avoid problems with non-sticky sessions.

Chrome extension API for filepicker

There is an API for download handling for google chrome:
https://developer.chrome.com/docs/extensions/reference/downloads/
This is great and it works. But I would love to add a listener for whenever a user triggers an <input type="file /> as well. This can be done with some client side script in content_scripts, but I need to do this with the API and not custom logic
There are two APIs that sounds right but only works for ChromeOS: fileBrowserHandler and fileSystemProvider
// WORKS
chrome.downloads.onCreated.addListener((item) => {
alert(1)
})
// Doesn't work
chrome.fileBrowserHandler.onExecute.addListener((id, details) => {
alert(2)
})
// Doesn't work
chrome.fileSystemProvider.onOpenFileRequested.addListener(() => {
alert(3)
})

Best way to implement notification alerts in Node/Express app?

I have inherited a Node/Express code base and my task is to implement a notification alert in the navigation menu. The app database has a table of 'pending accounts', and the alert needs to expose the number of these pending accounts. When an account is 'approved' or 'denied', this notification alert needs to update and reflect the new total of pending accounts.
I know how to do the styling and html here, my question is how best to instantiate, maintain and pass a global dynamic variable that reflects the number of pending accounts, and how to get this variable exposed in the header view which contains the navbar where the notification is to be displayed.
This project a pretty standard Node/Express app, however it uses the view engine Pug. At the root of the view hierarchy is a layout.pug file, which loads most of the scripts and stylesheets, and this layout view in turn loads the header Pug view. When this header view loads, and every time it loads, I need this updated 'pending accounts count' value available to insert into the header view. This is what I am at a bit of a loss on how to go about.
Below is the layout.pug markup with the inclusion of the header pug view. Everything else in the project is pretty straightforward vanilla Node/Express I believe, but I am not very experienced with this stack so if any other code is needed please don't hesitate to ask and I will post. Thanks.
doctype html
html(lang="en")
head
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(name='viewport', content='width=device-width, initial-scale=1.0, shrink-to-fit=no')
meta(name='theme-color', content='#4DA5F4')
meta(name='csrf-token', content=_csrf)
script(src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js")
block head
body
include partials/header
I tried including a script in my header.pug view, which contains the navbar element that I want to append the notification too...
link(href='/css/header/header.css' rel='stylesheet')
script(src='/js/registration/registrationsTable.js')
...
Which would run the following function on DOM load....
function getNumberOfPendingRegistrations() {
axios({
method: 'get',
url: '/admin/getNumberOfPendingRegistrations'
}).then((response) => {
if (response.status === 200) {
const { numPendingUsers } = response.data;
console.log(response.data);
} else {
console.log(response.status);
}
})
.catch((error) => {
console.error(error);
});
}
(() => {
document.addEventListener('DOMContentLoaded', () => {
getNumberOfPendingRegistrations();
which would then call the following express function....
exports.getNumberOfPendingRegistrations = (req, res) => {
RegisteredUser.find({ status: 'PENDING' }, (err, allPendingUsers) => {
if (!err) {
let numPendingUsers = 0;
allPendingUsers.forEach(() => {
numPendingUsers++;
});
return res.send(200, { numPendingUsers });
}
console.log('error');
throw err;
});
};
which would then return numPendingUsers to the axios then() function and make that variable available to the header.pug view here....
li.nav-item
a.nav-link(href='/admin/registeredUsers')
span.fa-solid.fa-pen-to-square(style="font-size: 0.7em;")
span(style="margin-left: 0.1em;") Registrations
span.notification=numPendingUsers
NumPendingUsers returns correctly to the axios .then() promise, but is somehow never made available in the header.pug view. It is always undefined. I'm not sure if its a timing issue w when the DOM is loaded, or if I'm making the variable available in .then() incorrecly or what. And also I feel like there must be a simpler way to accomplish all of this.
Figured out that I simply needed to implement a middleware to pass this data to every route. Duh.

Using res.download()

I have a route that goes like this:
exports.downloadFile = app => {
app.get('/install/download', (request, result) => {
result.download(`${__dirname}../../../public/exec/install.exe`, error => {
if (error) console.log("[FAILURE][RESOURCE] Could not download installation file\r\n");
else console.log("[SUCCESS][RESOURCE] Installation file has been downloaded\r\n");
});
});
}
With this route, I am trying to make it that when I click on a button this file is downloaded. When I click the button I get a message indicating SUCCESS, but my browser makes no indication of any download and does not prompt me to do anything. Is there some additional component I need to add to make sure it works?
This is the code on my client side to do the fetching:
const downloadFile = () => {
const url = `${window.location.protocol}//${window.location.host}/install/download`;
fetch(url)
.catch(error => alertMessage(error.message));
}
I am assuming it might have to do with me having no .then to handle the fetch, but I am not sure what I should be adding there and I am under the impression from the documentation that the browser automatically handles this part?
You forget to pass one download name argument.
Please modify download function as below.
result.download(`${__dirname}../../../public/exec/install.exe`, 'install.exe', error => {
if (error) console.log("[FAILURE][RESOURCE] Could not download installation file\r\n");
else console.log("[SUCCESS][RESOURCE] Installation file has been downloaded\r\n");
});

Angular UI Client Side Pagination

I would like to enable pagination and I'm torn between client side and server side pagination. In the long term (more data) it is probably better to do server side pagination, but I haven't found a good tutorial on it.
I use Angular/Express/Mongo. I have the Boostrap UI in use, and would like to use their pagination directive for pagination. I have read some articels on how to kind of do it, but they are outdated and I cannot get it to work. http://fdietz.github.io/recipes-with-angular-js/common-user-interface-patterns/paginating-through-client-side-data.html
Could anybody help me get that example to work with Bootstrap UI for Angular?
If you have a set number of items per page, you could do it this way :
Define an angular service to query the data on your server.
.factory('YourPaginationService', ['$resource',
function($resource) {
return $resource('baseUrl/page/:pageNo', {
pageNo: '#pageNo'
});
}
]);
Call it via the angular controller. Don't forget to inject your service, either globally or in the controller.
$scope.paginationController = function($scope, YourPaginationService) {
$scope.currentPage = 1;
$scope.setPage = function (pageNo) {
$scope.currentPage = pageNo;
YourPaginationService.query({
pageNo: '$scope.currentPage'
});
};
};
On express 4 (if you have it), set up your route.
app.route('/articles/page/:pageNo')
.get(data.listWithPagination) //random function name
Then you need to wire that function with the desired Mongo request in your Node controller. If you have Mongoose, it works like this :
exports.listWithPagination = function(req, res) {
var pageLimit = x; //Your hardcoded page limit
var skipValue = req.params.pageNo*pageLimit;
YourModel.find() //Your Mongoose model here, if you use Mongoose.
.skip(skipValue)
.limit(pageLimit)
.exec(function(err, data) {
if (err) {
return res.send(400, {
message: getErrorMessage(err)
});
} else {
res.jsonp(data);
}
});
};
That's how I would do it on a typical MEAN stack. If you're working with different libraries/technologies, you might need to adapt a few things.

Resources