Cancel grpc call from client on server streaming - javascript

I am using nodejs grpc server streaming to deliver realtime data from server to the clients. As the clients want to listen to the data, they should call a server function which returns a server stream. Then if the client want to end the listening I guess it should call the end method on the call object. But unfortunately I couldn't find any clue on the documentation about how to do this. I have tried call.end and call.destroy and call.cancel but end and destroy did nothing and cancel throws the following error. The sample client side code is as following:
function getData(token, userId) {
const data = {
token,
userId
}
let call = DataService.getData(data)
call.on('data', res => {
console.log(res)
})
call.on('status', console.log);
call.on('error', console.log);
setTimeout(() => {
console.log('destroy')
call.cancel()
}, 5000)
}
and the error is:
Error: 1 CANCELLED: Cancelled on client
at Object.callErrorFromStatus (...grpc\node_modules\#grpc\grpc-js\build\src\call.js:31:26)
at Object.onReceiveStatus (...grpc\node_modules\#grpc\grpc-js\build\src\client.js:330:49)
at Object.onReceiveStatus (...grpc\node_modules\#grpc\grpc-js\build\src\client-interceptors.js:299:181)
at ...grpc\node_modules\#grpc\grpc-js\build\src\call-stream.js:145:78
at processTicksAndRejections (internal/process/task_queues.js:75:11) {
code: 1,
details: 'Cancelled on client',
metadata: Metadata { internalRepr: Map(0) {}, options: {} }
}

Related

Error while trying to get realtime data from firestore database code: 'ERR_HTTP_HEADERS_SENT'

i am trying to build an app and i am very new to all this. So I've built a very simple function to get data from firestore and it works fine from a moment. This is the code:
async getData(req, res) {
const dataRef = db.collection(`${req.body.banco}`)
let result = []
dataRef.onSnapshot(docSnapshot => {
docSnapshot.forEach(doc => {
const data = doc.data()
result.push(data)
})
console.log(result)
return res.status(200).send(result)
}, (error) => {
console.log(`Erro encontrado: ${error}`)
})
}
My problem is that when I try to update any field from the document. It gets updated but I end up receiving this error:
node:_http_outgoing:576
throw new ERR_HTTP_HEADERS_SENT('set');
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (C:\Users\paulo\Desktop\sirius-back\functions\node_modules\express\lib\response.js:794:10)
at ServerResponse.send (C:\Users\paulo\Desktop\sirius-back\functions\node_modules\express\lib\response.js:174:12)
at ServerResponse.json (C:\Users\paulo\Desktop\sirius-back\functions\node_modules\express\lib\response.js:278:15)
at ServerResponse.send (C:\Users\paulo\Desktop\sirius-back\functions\node_modules\express\lib\response.js:162:21)
at C:\Users\paulo\Desktop\sirius-back\functions\src\controller\createDocs.js:70:40
at QueryWatch.onNext (C:\Users\paulo\Desktop\sirius-back\functions\node_modules\#google-cloud\firestore\build\src\reference.js:1914:13)
at QueryWatch.pushSnapshot (C:\Users\paulo\Desktop\sirius-back\functions\node_modules\#google-cloud\firestore\build\src\watch.js:469:18)
at QueryWatch.onData (C:\Users\paulo\Desktop\sirius-back\functions\node_modules\#google-cloud\firestore\build\src\watch.js:353:26) {
code: 'ERR_HTTP_HEADERS_SENT'
}
the app crashes and i have to start it over.
What is the reason of this? How can i get realtime updates from firestore databse after I update something from?
In Express you handle each requests by sending a single response to it. Since res a a response object, you can only send a response to the caller once. But since you're using onSnapshot, your code gets called for every change to the data too.
So initially, you load the data and send a response to the caller and all is 👍
But then when an update is made to the database, your code executes again and tries to send another response to the caller - and this is when you get an error.
The solution is to read the data only one with something like:
async getData(req, res) {
const dataRef = db.collection(`${req.body.banco}`)
let result = []
dataRef.get().then((docSnapshot) => {
docSnapshot.forEach((doc) => {
const data = doc.data()
result.push(data)
})
console.log(result)
return res.status(200).send(result)
}, (error) => {
console.log(`Erro encontrado: ${error}`)
})
}
Or a bit simplified:
async getData(req, res) {
const dataRef = db.collection(`${req.body.banco}`)
dataRef.get().then((docSnapshot) => {
const result = docSnapshot.docs.map((doc) => doc.data());
return res.status(200).send(result)
}, (error) => {
console.log(`Erro encontrado: ${error}`)
})
}
Once you've called res.send(...) the request is complete and the client stops listening. Sending further updates to a response is not possible with an Express request/response model like you're using.
Also see:
How to send multiple responses while computing in Express.js?
Sending multiple responses(res.json) with the same response object in Express.js
and more from searching for how to send multiple responses for a single request in express
You'll need to choose an infrastructure that allows a client to keep listening. Heads up: building something like that is quite involved, and you're likely better off if you use the client-side Firestore SDK to implement such realtime listeners

AstraDB failed GET request

I have been working on a TikTok clone app. So I created my database with Astra DB and set up two functions inside a function folder to test out if my posts are working. I am using netlify dev to test out the applications. But when I redirect http://localhost:8888/.netlify/functions/addData
I get this failed get request error
Request from ::1: GET /.netlify/functions/addData
Error: Request Failed: [object Object]
Stack Trace: Request failed with status code 401
at axiosRequest (D:\tiktokclone\node_modules\#astrajs\rest\src\rest.js:126:11)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async AstraClient._request (D:\tiktokclone\node_modules\#astrajs\rest\src\rest.js:199:22)
at async AstraClient.put (D:\tiktokclone\node_modules\#astrajs\rest\src\rest.js:263:12)
at async AstraCollection._put (D:\tiktokclone\node_modules\#astrajs\collections\src\collections.js:69:22)
at async Object.exports.handler (D:\tiktokclone\functions\addData.js:17:9)
Response with status 500 in 231 ms.
I quite don't understand what causes this. All the credentials inside my .env folder were correct.Here is the code I used to make the request
const { createClient } = require("#astrajs/collections");
const collection = "posts";
exports.handler = async function (event, context, callback) {
const astraClient = await createClient({
astraDatabaseId: process.env.ASTRA_DB_ID,
astraDatabaseRegion: process.env.ASTRA_DB_REGION,
applicationToken: process.env.ASTRA_DB_APPLICATION_TOKEN,
});
console.log(astraClient)
console.log(collection)
console.log('Hello')
const posts = astraClient
.namespace(process.env.ASTRA_DB_KEYSPACE)
.collection(collection);
try {
await posts.create("a post", {
title: "my first post",
});
return {
statusCode: 200,
};
} catch (e) {
console.error(e);
return {
statusCode: 500,
body: JSON.stringify(e),
};
}
};
I found a fix. For some reason, I was trying to call the API using an application token and it was giving me the 401 error. When I used username and password it worked.
const astraClient = await createClient({
astraDatabaseId: process.env.ASTRA_DB_ID,
astraDatabaseRegion: process.env.ASTRA_DB_REGION,
username: process.env.ASTRA_DB_USERNAME,
password: process.env.ASTRA_DB_PASSWORD,
});
username is the client ID and password is the client secret. This error happened because of a slight confusion with the REST API and the Document API. Astra DB uses application token for authenticating document API while REST API uses client ID and Password.

Javascript: How can I interact with a TCP Socket API using net.connect properly?

I'm fairly new to Javascript and am trying to wrap my head around async, promises, etc.
I have an application running a TCP API (non-HTTP) on the localhost. I'm building an Electron app to interact with this API. I need to send a single request to the API every second and retrieve a single JSON object it returns.
I'm able to do this successfully (for while) by running something like this:
const net = require('net');
function apiCall() {
if (running) {
setTimeout(() => {
// Send the request
request = '{"id":1,"jsonrpc":"2.0","method":"getdetails"}'
socketClient = net.connect({host:'localhost', port:8888}, () => {
socketClient.write(request + '\r\n');
});
// Listen for the response
var response;
socketClient.on('data', (data) => {
response = JSON.parse(data).result;
updateUI(response);
socketClient.end();
});
// On disconnect
socketClient.on('end', () => {
console.log('Disconnected from API');
});
apiCall();
}, refreshRate)
}
}
After running this for an extended amount of time, it appears that the API server is crashing:
Error: connect ECONNREFUSED 127.0.0.1:8888
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1146)
Unfortunately, I have no control over the API server or its source code. I'd like some clarification on whether my client might be causing the API server to crash by sending requests this way.
Should I be opening and closing the connection for each request or keep it open and send requests only every second?
If I should be keeping the connection open, how can I do this, and do I need to worry about keep-alive?
It looks like that every time you call apiCall you are creating a new socket client and you are not removing the old socket client instances. This is a memory leak and it will cause the application to crash after running for some time
You can keep a running connection instead like below
const net = require("net");
const { once } = require("events");
let socketClient;
function apiCall() {
if (running) {
setTimeout(async () => {
const request = '{"id":1,"jsonrpc":"2.0","method":"getdetails"}';
// Create the socket client if it was not already created
if (!socketClient) {
socketClient = net.connect({ host: "localhost", port: 8888 });
// On disconnect
socketClient.on("end", () => {
console.log("Disconnected from API");
socketClient.destroy();
socketClient = null;
});
// Wait until connection is established
await once(socketClient, "connect");
}
// Send the request
socketClient.write(request + "\r\n");
// Listen for the response
const data = await once(socketClient, "data");
const response = JSON.parse(data).result;
updateUI(response);
apiCall();
}, refreshRate);
}
}

Uncaught (in promise) Error: Failed to invoke 'XXXXX' due to an error on the server

I created a .net core chat application using SignalR and I used WebRTC for video calls. As I need to send the SDP to the receiver using server method so I created a hub method call "SendOffer". When I click Video call button I have invoked this "SendOffer" method. I have put the client side code below
var connection = new signalR.HubConnectionBuilder()
.withUrl('/chat')
.build();
const Peer = new RTCPeerConnection();
const video = document.querySelector('video');
const constraints = {
'video': true,
'audio': true
}
document.getElementById("sendVideo").addEventListener("click", function (event) {
navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
}).then(function (stream) {
video.srcObject = stream
video.play();
//Peer.addStream(stream);
Peer.createOffer()
.then(sdp => Peer.setLocalDescription(sdp))
.then(function () {
console.log(Peer.localDescription);
//connection.invoke("SendOffer", Peer.localDescription).catch(function (err) {
// return console.error(err.toString());
connection.invoke("SendOffer", Peer.localDescription);
})
});
})
But this gives an error in the console log and not working. blow is the error
signalr.js:2088 Uncaught (in promise) Error: Failed to invoke
'SendOffer' due to an error on the server.
at _this.callbacks. (signalr.js:2088)
at HubConnection.processIncomingData (signalr.js:2182)
at WebSocketTransport.HubConnection.connection.onreceive (signalr.js:1905)
at WebSocket.webSocket.onmessage (signalr.js:3949)
Can any one please help me to solve this error.
Had the same error.
In my case that was because of type mismatch. Server expected string. Client was sending value from variable with value '1234', that was sending as a int.

Twilio voice call works only once [microphone permission issue]

I implemented Twilio voice call functionality that enables users to call support people from the browser but it works the first time only after allowed microphone permission but then next time getting below error and resetting microphone permission then voice call works.
code: 31000, message: "Cannot establish connection. Client is disconnected"
Below is code snippet on the client-side that written in angular with help of twilio client docs
import twilio from 'twilio-client';
public device: any;
this.device = new twilio.Device('<token-fetched>', {
codecPreferences: ['opus', 'pcmu'],
fakeLocalDTMF: true,
enableIceRestart: true
})
let params = {
To: '<to-number>',
Id: '<id>',
token: '<token-fetched>'
}
if (this.device) {
this.device.connect(params);
}
this.device.on('error', (error) => {
console.log("this is error",error);
})
this.device.on('disconnect',(connection) => {
console.log("connection ended", connection);
})
I had the same issue here, the problem is that I was calling var connection = Twilio.Device.connect(); before the async setup work had been completed.
Write device.connect inside of device.ready function like
Twilio.Device.ready(function(device) {
console.log('Ready');
var connection = Twilio.Device.connect();
// Do rest of twilio work ...!
}
This will fix your issue.

Categories

Resources