AJAX Receiving multiple NodeJS Responses for Single Request - javascript

I am building a WebApp that includes heavy AJAX calling from the frontend and NodeJS Express at the backend. My Frontend Code looks like this-
This is the global AJAX function I am using in my all projects-
function _ajax(params = {}, next = null, url = '', method = 'post', carry = null) {
params = this._isObjectValid(params, 1, -1) ? params : {};
for (let key in params) {
if (params.hasOwnProperty(key)) {
const checkObject = params[key];
if (typeof checkObject === 'object' || Array.isArray(checkObject)) {
params[key] = JSON.stringify(checkObject);
}
}
}
const httpRequest = new XMLHttpRequest();
httpRequest.open(method, url, true);
httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
const p = Object.keys(params).map(function (value) {
return encodeURIComponent(value) + '=' + encodeURIComponent(params[value]);
}).join('&');
if (next) {
httpRequest.onreadystatechange = function () {
next(httpRequest, carry);
};
}
httpRequest.send(p);
}
This is the global Click Event Binding function
function _click(next, target, node, extra = null) {
node.onclick = (event) => {
next(event, target, extra);
};
return node;
},
This is my AJAX Request
_click(
() => {
_ajax(
{mod: 'login'},
(request) => {
console.info(request.status);
console.info(request.response);
},
'http://localhost:1008/'
)
}, null, buttonSubmit);
My Backend Code for Handling Post AJAX Requests is:
app.post('*', async (req, res) => {
console.info('POST RECEIVED');
const params = req.body;
console.info(params);
await posts(req, res, params, dbo);
});
export async function posts(req, res, params, dbo) {
if (res) {
const mod = params.mod;
switch (mod) {
case 'user':
await sendPass(res, 'User Will', null);
break;
default:
await send(res, 'Invalid Module Call', null);
}
}
}
export function send(res, message, data, result = false) {
res.send({result: result, message: message, data: data});
res.end();
}
export function sendError(res, message, data) {
send(res, message, data, false);
}
export function sendPass(res, message, data) {
send(res, message, data, true);
}
Now in any other server like PHP or .NET, my web app is getting exactly one response from the server when I click the button, but for NodeJS I am getting three responses while I am processing the AJAX request only once-
This is repeating for every AJAX Request. So If I'm processing 3 AJAX Requests, my Web App is receiving 9 responses. I tried to search on the internet on this but can't find much. Since this scenario is not repeating on any other Server except NodeJs, so it may be not a problem from JavaScript event binding or ajax processing, or a browser issue.
Any suggestion will be appreciable. Thanks in advance.

I found the bug in the global AJAX function. I need to detect the status of readyState of httpRequest. I'm posting an updated version of the function:
Before:
httpRequest.onreadystatechange = function () {
next(httpRequest, carry);
};
After:
/**
* readyState values
* 0: request not initialized
* 1: server connection established
* 2: request received
* 3: processing request
* 4: request finished and response is ready
*/
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4)
next(httpRequest, carry);
};
Surprisingly, it was not creating problems in other servers like WAMP or .NET for reasons I still don't know.

Related

Firebase Functions Event Source stuck on pending

I'm trying to use an event emitter to trigger specific functions that I need.Here is the nodejs code.
Event source functions locally on localhost but once deployed, I cannot detect the event triggers. However, the logs on the firebase functions logger show the function being carried out. However, when tested, the function does not provide any feedback to the event
const EventEmitter = require('events');
const Stream = new EventEmitter();
// 'Access-Control-Allow-Origin': '*',
// 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
exports.eventEmitter = (req, res) => {
console.log(req.body);
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
})
Stream.on('push', function(event, data) {
console.log('send0');
res.write('event: ' + String(event) + '\n' + 'data:' + JSON.stringify(data) + '\n\n');
});
setInterval(() => {
console.log('send1');
Stream.emit('push', 'message', { msg: 'it works! hurraaay' });
}, 5000);
}
When I test this code locally for event emission, using the following Angular code, The event is detected and logged on the console. However, once I deploy the function to firebase functions, I get a pending request on the network tab of the browser that doesn't have any log and seems to not complete at all and my angular code does inevitably detects nothing
getStream() {
return Observable.create(observer => {
let url = 'link to emitter';
let eventSource = new EventSource(url);
eventSource.addEventListener('message', function(e) {
console.log(e.data);
}, false);
eventSource.addEventListener('open', function(e) {
// Connection was opened.
console.log("open");
}, false);
eventSource.addEventListener('error', function(e) {
if (eventSource.readyState == EventSource.CLOSED) {
// Connection was closed.
console.log("closed");
}
}, false);
eventSource.addEventListener('message', message => { console.log(message.data); });
eventSource.onmessage = (event) => {
let json = JSON.parse(event.data);
if (json !== undefined && json !== '') {
this.zone.run(() => observer.next(json));
}
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
observer.complete();
} else {
observer.error('EventSource error: ' + error);
}
}
});
}
Cloud Functions HTTP endpoints do not support streaming data to clients like a websocket. It can only receive an entire payload up front, then send an entire payload back to the client. There are no chunked responses. The limit for the payload is 10MB. The default timeout for a function invocation is 60s and can only be increased to 9m max. If you need streaming, then Cloud Functions is not the right product for your use case.

How to integrate getAccessToken with fetch function to load data from DRF backend to React Frontend?

React newbie here, but proficient in Django.I have a simple fetch function which worked perfectly but then my project had no login authentication involved. Now that I have configured the login system, my backend refuses to serve requests with any access tokens. My login authentication is very new to me and was more or less copied from somewhere. I am trying to understand it but am not able to. I just need to know how to convert my simple fetch function to include the getAccessToken along the request in it's headers so my backend serves that request.
Here is my previously working simple fetch function :
class all_orders extends Component {
state = {
todos: []
};
async componentDidMount() {
try {
const res = await fetch('http://127.0.0.1:8000/api/allorders/'); // fetching the data from api, before the page loaded
const todos = await res.json();
console.log(todos);
this.setState({
todos
});
} catch (e) {
console.log(e);
}
}
My new login JWT authentication system works perfectly, but my previous code is not working and I keep getting error
"detail": "Authentication credentials were not provided."
This is is the accesstoken I am not able to 'combine' with my preivous fetch function:
const getAccessToken = () => {
return new Promise(async (resolve, reject) => {
const data = reactLocalStorage.getObject(API_TOKENS);
if (!data)
return resolve('No User found');
let access_token = '';
const expires = new Date(data.expires * 1000);
const currentTime = new Date();
if (expires > currentTime) {
access_token = data.tokens.access;
} else {
try {
const new_token = await loadOpenUrl(REFRESH_ACCESS_TOKEN, {
method: 'post',
data: {
refresh: data.tokens.refresh,
}
});
access_token = new_token.access;
const expires = new_token.expires;
reactLocalStorage.setObject(API_TOKENS, {
tokens: {
...data.tokens,
access: access_token
},
expires: expires
});
} catch (e) {
try {
if (e.data.code === "token_not_valid")
signINAgainNotification();
else
errorGettingUserInfoNotification();
} catch (e) {
// pass
}
return reject('Error refreshing token', e);
}
}
return resolve(access_token);
});
};
If you're looking for a way how to pass headers in fetch request, it's pretty straight forward:
await fetch('http://127.0.0.1:8000/api/allorders/', {
headers: {
// your headers there as pair key-value, matching what your API is expecting, for example:
'details': getAccessToken()
}
})
Just don't forget to import your getAccessToken const, if that's put it another file, and I believe that would be it. Some reading on Fetch method

Send proactive message based on setInterval data for Bot v4

So I'm trying to migrate my bot to v4 whilst adding a livechat functionality to it.
My use case is this:
I have a secondary non-azure bot which contains all my knowledge base, triggers and responses. This secondary bot has an API for direct communication, one of it's methods handles direct messages to the bot, let's call it "Talk". Then there is another method that fetches data from a livechat, this one we'll call "Poll", you can "Poll" a livechat each second and if there is a response there, you'll get it, if not, the API responds blank, which is acceptable and I'm ready to handle it in my code.
Where I'm stuck: I can send and receive messages to the Bot using "Talk", I can even start Polling a livechat conversation and see what's there. But I need to get what's there and send it back to the user.
I've tried declaring the context as "global" but it seems like i'm doing something wrong, I receive: TypeError: Cannot perform 'set' on a proxy that has been revoked
This is my Talk function
It works as expected, meaning that it always sends information to the secondary bot when the user types something to the AzureBot v4.
It also triggers the islivechat to true when it receives a SpecialAction to start a livechat (handled by the secondary bot).
async function Talk(context, next) {
var Talk_parameters = {
"type": "talk",
"parameters": {
"userUrl": "aHR0cHM6Ly9zdGF0aWw=",
"alreadyCame": true,
"os": "V2luZG93cyAxMC4w",
"browser": "RmlyZWZveCA2OA==",
"disableLanguageDetection": true,
"contextType": "V2Vi",
"mode": "U3luY2hyb24=",
"botId": "a3f3b9fb-********-5f6ba69bb9da",
"qualificationMode": true,
"language": "cHQ=",
"space": "Default",
"solutionUsed": "QVNTSVNUQU5U",
"clientId": "UEZIdDhrMXVVbGU5b0lw",
"useServerCookieForContext": false,
"saml2_info": "",
"pureLivechat": false,
"userInput": Buffer.from(VArequestData.parameters.userInput).toString('base64'),
"templateFormats": "",
"contextId": VArequestData.parameters.contextId,
"timestamp": new Date().valueOf()
}
}
console.log(Talk_parameters);
return new Promise(resolve => {
request({
url: "https://sma********.com/servlet/chatHttp",
method: "GET",
body: Talk_parameters,
headers: {},
json: true
}, async function (error, response, body) {
if(!error)
resolve(body);
})
}).then(response_values => {
console.log(response_values);
VAresponseData.text = Buffer.from(response_values.values.text,'base64')
VAresponseData.context = response_values.values.contextId
VArequestData.parameters.contextId = response_values.values.contextId
if (response_values.values.specialAction == "U3RhcnRQb2xsaW5n"){
islivechat = true;
}
})
}
the SetInterval part
setInterval(async function(){
if (islivechat == true){
Poll()
console.log("Polling data!")
msg();
}
}, 1000);
the Poll function
When islivechat == true this guy runs each second, fetching data from the conversation with the secondary bot. I can get the exact value I need in the line "VAresponseData.livechatText = response_values.values.text", but I can't send it back to the user.
async function Poll(context) {
// Setting URL and headers for request
var Polling_parameters = {
type:"poll",
parameters:{
lastPoll: VArequestData.parameters.lastPoll,
mode : "Polling",
contextId: VArequestData.parameters.contextId,
botId : VArequestData.parameters.botId,
qualificationMode : VArequestData.parameters.qualificationMode,
language : VArequestData.parameters.language,
space : VArequestData.parameters.space,
solutionUsed :"LIVECHAT",
timestamp : new Date().valueOf()
}
}
console.log(Polling_parameters);
return new Promise(resolve => {
request({
url: "https://sma**************.com/servlet/chatHttp",
method: "POST",
body: Polling_parameters,
headers: {},
json: true
}, async function (error, response, body) {
if(!error)
resolve(body);
})
}).then(async (response_values) => {
if(response_values.values.specialAction == "RW5kUG9sbGluZw=="){
islivechat = false;
}
console.log(response_values);
console.log("CONTEXT")
console.log(response_values.values.contextId);
console.log(Polling_parameters.parameters.contextId);
VArequestData.parameters.lastPoll = response_values.values.serverTime
VAresponseData.livechatText = response_values.values.text
console.log('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx >>> ' + response_values.values.text)
sendLivechatMsg();
})
}
My failed attempt:
This function is declared outside of the main code, meaning it is accessible to any other function, but I receive TypeError: Cannot perform 'set' on a proxy that has been revoked when I run it. I tried running a console.log for it and the code doesn't even reach that part.
async function sendLivechatMsg(globalContext){
context = globalContext;
await msg(VAresponseData.livechatText, context)
.then(async function(){
context.sendActivity(VAresponseData.livechatText)
.then(
console.log('activity')
)
})
await next();
}
The main stage
I used the sample code for Bot v4 and changed just a few things
class EchoBot extends ActivityHandler {
constructor() {
super();
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
this.onMessage(async (context, next) => {
VArequestData.parameters.userInput = context.activity.text;
await Talk(context, next)
.then(async function(data){
context.sendActivity(`'${ VAresponseData.text }'`)
// context.sendActivity(`'${ VAresponseData.context }'`)
}, error => {
console.log('ERROR: ' + error)
})
await next();
}
);
// this.onMembersAdded(async (context, next) => {
// setTimeout(() => startPolling(context), 3000);
// const membersAdded = context.activity.membersAdded;
// for (let cnt = 0; cnt < membersAdded.length; ++cnt) {
// if (membersAdded[cnt].id !== context.activity.recipient.id) {
// // await context.sendActivity('Hello and welcome!');
// }
// }
// // By calling next() you ensure that the next BotHandler is run.
// await next();
// });
this.onTurn
}
}
The expected scenario is:
If the user is in a livechat and the variable "VAresponseData.livechatText" has a value, it needs to be sent to the user immediately, without the need for him to "Ask".
But I can't figure out a way to sent this back to the user.

Stubbing request across entire project

I'm writing tests for a small REST library that implements OAuth's refresh grant on top of the request library. As part of it's functionality, it's providing a retry function that has something like this in rest.js:
const auth = require('./auth');
const request = require('request');
function retry(headers, responseHandler) {
headers.auth.bearer = auth.getToken();
request(headers, function(err, resp) {
if (resp.error && resp.error == 'invalid_token') {
return auth.renew(function(e, r) { retry(headers, responseHandler); });
}
// handle happy case
});
});
What auth.renew does is posting to the token provider with the information in headers and internally known refresh token. The important bit is that it does this by using request.post, passing the recursive event handler down to the request library.
Naturally, when I test this, I don't want outgoing HTTP calls. So I use sinon to stub them out:
const requestStub = sinon.stub(),
rest = proxyquire('./rest', { 'request': requestStub }),
authFail = { error: 'invalid_token' },
authSuccess = { token: '123' };
describe('#retry', () => {
it('calls retry twice on failed auth', () => {
requestStub.yields(null, authFail);
requestStub.post = sinon.stub().yields(null, authSuccess);
retry({}, () => {});
sinon.assert.calledTwice(requestStub);
});
});
The problem is that auth.renew happily goes on to require('request') on it's own and thus never seeing my stub. So I guess my question is:
How do I make auth use my stubbed request instead of its own?
I know sinon can stub XHR, but that seems like a lot of low-level effort for what I want.
So the solution I came up with was to replace the entire auth.renew method, so the thing looks like this:
const requestStub = sinon.stub(),
rest = proxyquire('./rest', { 'request': requestStub,
'./auth': {
renew: function(callback) {
requestStub.yields(null, authSuccess);
callback();
}
}),
authFail = { error: 'invalid_token' },
authSuccess = { token: '123' };
describe('#retry', () => {
it('calls retry twice on failed auth', () => {
requestStub.yields(null, authFail);
retry({}, () => {});
sinon.assert.calledTwice(requestStub);
});
});

Push WebAPI + IndexedDB + ServiceWorker

I've implemented the Push WebAPI in my web application using Service Worker as many articles explain on the web.
Now I need to store some data inside IndexedDB to make them available while the web app is closed (chrome tab closed, service worker in background execution).
In particular I would like to store a simple url from where retrieve the notification data (from server).
Here is my code:
self.addEventListener("push", (event) => {
console.log("[serviceWorker] Push message received", event);
notify({ event: "push" }); // This notifies the push service for handling the notification
var open = indexedDB.open("pushServiceWorkerDb", 1);
open.onsuccess = () => {
var db = open.result;
var tx = db.transaction("urls");
var store = tx.objectStore("urls");
var request = store.get("fetchNotificationDataUrl");
request.onsuccess = (ev) => {
var fetchNotificationDataUrl = request.result;
console.log("[serviceWorker] Fetching notification data from ->", fetchNotificationDataUrl);
if (!(!fetchNotificationDataUrl || fetchNotificationDataUrl.length === 0 || !fetchNotificationDataUrl.trim().length === 0)) {
event.waitUntil(
fetch(fetchNotificationDataUrl, {
credentials: "include"
}).then((response) => {
if (response.status !== 200) {
console.log("[serviceWorker] Looks like there was a problem. Status Code: " + response.status);
throw new Error();
}
return response.json().then((data) => {
if (!data) {
console.error("[serviceWorker] The API returned no data. Showing default notification", data);
//throw new Error();
showDefaultNotification({ url: "/" });
}
var title = data.Title;
var message = data.Message;
var icon = data.Icon;
var tag = data.Tag;
var url = data.Url;
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: tag,
data: {
url: url
},
requireInteraction: true
});
});
}).catch((err) => {
console.error("[serviceWorker] Unable to retrieve data", err);
var title = "An error occurred";
var message = "We were unable to get the information for this push message";
var icon = "/favicon.ico";
var tag = "notification-error";
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: tag,
data: {
url: "/"
},
requireInteraction: true
});
})
);
} else {
showDefaultNotification({ url: "/" });
}
}
};
});
Unfortunately when I receive a new push event it doesn't work, showing this exception:
Uncaught DOMException: Failed to execute 'waitUntil' on 'ExtendableEvent': The event handler is already finished.
at IDBRequest.request.onsuccess (https://192.168.0.102/pushServiceWorker.js:99:23)
How can I resolve this?
Thanks in advance
The initial call to event.waitUntil() needs to be done synchronously when the event handler is first invoked. You can then pass in a promise chain to event.waitUntil(), and inside that promise chain, carry out any number of asynchronous actions.
Your current code invokes an asynchronous IndexedDB callback before it calls event.waitUntil(), which is why you're seeing that error.
The easiest way to include IndexedDB operations inside a promise chain is to use a wrapper library, like idb-keyval, which takes the callback-based IndexedDB API and converts it into a promise-based API.
Your code could then look like:
self.addEventListener('push', event => {
// Call event.waitUntil() immediately:
event.waitUntil(
// You can chain together promises:
idbKeyval.get('fetchNotificationDataUrl')
.then(url => fetch(url))
.then(response => response.json())
.then(json => self.registration.showNotification(...)
);
});

Categories