How to set up heartbeats with webstomp-client and Spring Boot? - reactjs

I have a React.js frontend establishing websocket connections (STOMP) with a Spring Boot backend.
I'm using the webstomp-client library on the front-end, because that is the one recommended in the Spring Boot documentation.
I can establish the connection and send and receive messages. However, there are no outgoing heartbeats from the frontend, only incoming heartbeats being sent by the backend. After some time (~ 5 mins on real project, ~30 secs on minimum reproducible example), the connection is closed with the following error:
Whoops! Lost connection to ws://localhost:8081/test:
CloseEvent {isTrusted: true, wasClean: true, code: 1002, reason: "", type: "close", …}
bubbles: false
cancelBubble: false
cancelable: false
code: 1002
composed: false
currentTarget: WebSocket {url: "ws://localhost:8081/test", readyState: 3, bufferedAmount: 0, onerror: null, onopen: ƒ, …}
defaultPrevented: false
eventPhase: 0
isTrusted: true
path: []
reason: ""
returnValue: true
srcElement: WebSocket {url: "ws://localhost:8081/test", readyState: 3, bufferedAmount: 0, onerror: null, onopen: ƒ, …}
target: WebSocket {url: "ws://localhost:8081/test", readyState: 3, bufferedAmount: 0, onerror: null, onopen: ƒ, …}
timeStamp: 49784.04500000033
type: "close"
wasClean: true
__proto__: CloseEvent
It looks like someone else was having the same problem here, but no SO response for them.
Oddly, if I set heartbeat: false on the webstomp-client initialiser, the incoming heartbeats will stop as well, but the connection remains open indefinitely... which kinda solves my problem but I'd feel a lot more comfortable if the heartbeats were working.
I've tried various combinations of heartbeat settings on the front-end and back-end, but no joy.
According to the CloseEvent specifications, the code 1002 means that "The endpoint is terminating the connection due to a protocol error." Unfortunately, I don't know how to turn that bit of info into a solution.
The webstomp-client is setup like this:
export const connectWebstompClient = () => {
const websocketsAreNotAvailableInTheBrowser = typeof WebSocket !== 'function';
let stompClient: Client
if (websocketsAreNotAvailableInTheBrowser) {
console.log('Creating SockJS fallback')
stompClient = webstomp.over(new SockJS('http://localhost:8081/test'))
} else {
console.log('Creating websocket')
stompClient = webstomp.client('ws://localhost:8081/test', {
heartbeat: { incoming: 10000, outgoing: 10000},
// heartbeat: false,
protocols: webstomp.VERSIONS.supportedProtocols()
})
}
console.log('connecting webstomp client')
stompClient.connect({/* no headers */}, () => onConnect(), (error: Frame | CloseEvent) => onError(error))
const onConnect = () => {
console.log('subscribing to /topic/test')
stompClient.subscribe('/topic/test', (payload: Message) => showTextMessage(payload))
// Puts stomp client in the Redux store
store.dispatch<any>(addClient(stompClient));
}
const onError = (error: Frame | CloseEvent) => console.error(error)
return stompClient
}
Minimum reproducible example for the front-end is here, and for the back-end here.
Can anybody see what's going on? Your help would be much appreciated.

The problem is coming from this line:
protocols: webstomp.VERSIONS.supportedProtocols()
In fact, supportedProtocols() will return the following array: ['v10.stomp', 'v11.stomp', 'v12.stomp'].
When sending the connect frame to the stomp server, webstomp-client Version 1.2.6 (which I'm examinating) will fall back to the first supportedProtocols() entry i.e v10.stomp of the stomp protocol.
Unfortunately v10.stomp does not support heartbeat and that's why your browser is not sending PING in response to the server PONG.
To fix this problem, you can set the protocols option to 'v11.stomp' or 'v12.stomp' depending on your stomp backend (spring stomp) supported version:
protocols: ["v12.stomp"]
Or if not supported by backend:
protocols: ["v11.stomp"]
I hope that my response solves your issue.

Related

AxiosError when integrating Stripe with Next.js

I am relatively new to Next.js, and I though I have been encountering some bugs and issues here and there, I have been able to overcome most of them. The latest one I have not been able to figure out, so let's see if somebody else knows what's going on.
I am creating an e-commerce platform on Next.js, Redux and Axios. For the moment I am using fake data to populate the products. When creating a checkout session, the data of the items in the cart is pushed (I can console.log() and I see the items in the terminal. However, the mapping of the checkout session to Stripe is not working. The error I get is an AxiosError: Request failed with status code 500
Error message screenshot
I am trying to add the item data dynamically to the checkout session as follows:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async (req, res) => {
const { items, email } = req.body;
const transformedItems = items.map((item) => ({
description: item.description,
// if quantities are bundled, this needs to change.
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: item.price * 100,
product_data: {
name: item.title,
images: [item.image],
},
},
}));
const session = await stripe.checkout.sessions.create({
line_items: transformedItems,
mode: 'payment',
success_url: `${process.env.HOST}/success`,
cancel_url: `${process.env.HOST}/checkout`,
metadata: {
email,
images: JSON.stringify(items.map((item) => item.image)),
},
});
res.status(200).json({ id: session.id });
};
I have also tried copying the exact code from the Stripe documentation and implementing the changes, but this hasn't changed anything either.
I know, Stripe has made some changes to their API, and that for instance you can't specify anymore with statements like
payment_method_types: ["card"],
anymore. So I took it out.
I have not included any code from the checkout piece, as this seems to be working (as stated, it console.logs() just fine. I can provide this as well though, if someone thinks the issue might be there.
Thanks in advance.
Nela.
Thanks to Code-Apprentice and maiorano84 whose hints in the comments:
A status code 500 means there is an error on the backend. If the server is under your control, then you need to look at the server logs to see what the problem is. The server logs will have a stack trace that shows you where the problem occurs. If you need help understanding the stacktrace, you will need to include it in your question. – Code-Apprentice 22 hours ago
Is this a server-side or client-side AJAX request? If it's the latter, check your network tab to see the full output of your failed request (marked in red in Chrome Devtools). You should be able to get more information about the failed request there. If it's failing on the Stripe side, the Response Headers and Body should have more information there to help you debug. If it's failing on your own success and checkout callbacks, your server logs might have additional information that can help you. – maiorano84 22 hours ago
led me to the answer. I checked my console, and the error that was given was from Stripe. It read as follows:
StripeInvalidRequestError: You cannot use line_items.amount, line_items.currency, line_items.name, line_items.description, or line_items.images in this API version. Please use line_items.price or line_items.price_data.
So I moved the item.description I had outside of the product_data object, into it, and it worked.
The code looks now like this:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async (req, res) => {
const { items, email } = req.body;
const transformedItems = items.map((item) => ({
// if quantities are bundled, this needs to change.
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: item.price * 100,
product_data: {
name: item.title,
description: item.description,
images: [item.image],
},
},
}));
const session = await stripe.checkout.sessions.create({
line_items: transformedItems,
mode: 'payment',
success_url: `${process.env.HOST}/success`,
cancel_url: `${process.env.HOST}/checkout`,
metadata: {
email,
images: JSON.stringify(items.map((item) => item.image)),
},
});
res.status(200).json({ id: session.id });
};

axios post request to MongoDB Atlas error 11000

I am trying to send some data to MongoDB Atlas from a React frontend. I tested the backend (an Express server) with Postman. The routes and endpoints are working as expected, and I can create todos and see them in MongoDB-Atlas.
// createTodo.js
onSubmit(e) {
e.preventDefault()
const todo = {
todoTitle: this.state.todoTitle,
todoBody: this.state.todoBody,
}
console.log(todo)
axios.post('http://localhost:5000/api/todos', todo).then((res) => console.log(res.data))
this.setState({
todoTitle: '',
todoBody: '',
})
}
the (res.data) that I am console.logging gives me an object with a MongoError 11000 code.
Object { driver: true, name: "MongoError", index: 0, code: 11000, keyPattern: {…}, keyValue: {…} }
CreateTodo.js:40
Any one have experience with this type of error? Are there any online resources or guides to help resolve this one? Thank you.
I got this error one time when i defined a collection with a particular name and later on changed the name, hence i believe that mongoDB expected to receive a data attributed to that particular name but didn't and got that error. However, i managed to fix it after dropping the collection and run again.

Identity Server 4 Silent Renew ErrorResponse: login_required

I have cloned the repo from the redux-oidc-example and it works for the most part but after a few hours it gives the following error:
Action payload: ErrorResponse: login_required
at new e (oidc-client.min.js:1)
at t [as _processSigninParams] (oidc-client.min.js:1)
at t [as validateSigninResponse] (oidc-client.min.js:1)
at oidc-client.min.js:1
UserManager.js looks like this:
const userManagerConfig = {
client_id: 'js.dev',
client_secret: 'secret',
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/callback`,
response_type: 'id_token token',
scope: 'openid email profile role offline_access',
authority: 'http://localhost:8080',
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/silent_renew.html`,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true
};
and my identity server config:
{
"Enabled": true,
"ClientId": "js.dev",
"ClientName": "Javascript Client",
"ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ],
"AllowedGrantTypes": [ "implicit", "authorization_code" ],
"AllowedScopes": [ "openid", "email", "profile", "role", "offline_access" ],
"AllowOfflineAccess": true,
"AllowAccessTokensViaBrowser":true,
"RedirectUris": [
"http://localhost:8081/callback",
"http://localhost:8081/silent_renew.html"
],
"PostLogoutRedirectUris": [
"http://localhost:8081"
],
"AccessTokenLifetime": 900,
"RequireConsent": false
}
I noticed that prior to error last valid response had one cookie response(idsrv.session) with empty value with the expiry date set to the previous year:
I believe this to be the root cause of the issue, I searched it on related Github repo and tried to add the Cookie.SameSite to none but it didn't help:
services.AddAuthentication()
.AddSaml(Configuration,externalProviders.UseSaml)
.AddCookie(options => {
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.Cookie.SameSite = SameSiteMode.None;
});
Any idea!
This is likely due to your IDP session expiring - if you call the authorize endpoint with prompt=none but it's unable to satisfy that request because no valid session exists (i.e. authentication cookie does not exist or has expired) then it will return error=login_required.
If this occurs then the correct course of action is to do an interactive (i.e. prompt=login) sign in request in the top level browser window.
After searching the Identity Server 4 repo, I made the following changes to my code:
services.AddIdentityServer(options=>
{
options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
options.Authentication.CookieSlidingExpiration = true;
})
.AddProfileService<ProfileService>()
.AddSigningCertificate(Configuration)
.AddInMemoryClients(Configuration.GetSection("IdentityServer:Clients"))
.AddInMemoryIdentityResources(Resources.GetIdentityResources());
It started working afterward, but you would have to login again after you close the browser or reopen a new tab I guess it's because of the sessionStorage.
When the session expires the signin-callback is being called by STS having a query parameter called 'error' with the value 'login_required'.
In the signin-callback, before completing sign-in, you can check for this query parameter and if it's found you can sign-out also from your web client.
I had the same issue and tried the proposed above, but for me, it actually was SameSiteMode not set correctly on IdentityServer Cookies. It caused Callback error: ErrorResponse: login_required right after login and after N attempts user was logged out.
This helped me https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/host/Extensions/SameSiteHandlingExtensions.cs
What they do is based on this article https://devblogs.microsoft.com/dotnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
Hope this is useful.
Update.
I had another issue related to this when the user was logged out after re-opening a browser (especially on Android Chrome). login_required error was shown. I noticed that session cookie Expires/Max-Age was set to Session and not some future date. Probably because of that check session iframe (with src={identity server url}/connect/checksession) failed as Identity Server thought there was no session as cookie expired.
I tried setting cookie lifetime via options, but it didn't work as expected for some reason. Lifetime was always 14 days:
services.AddIdentityServer(options=>
options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
options.Authentication.CookieSlidingExpiration = true;
})
Then I tried this and it worked for me:
services.ConfigureApplicationCookie(options => {
options.ExpireTimeSpan = sessionCookieLifetime;
options.SlidingExpiration = true;
})

Network Error: expo-app-auth - Broken Request - React Native

I'm trying to oAuth through the Spotify api for my React-Native app, but continue to get a ExpoAppAuth.get Auth: Network Error message. I'm not understanding what the issue could be, or where in particular to look since the trace is so vague. My clientID and such are correct as well.
`import * as AppAuth from 'expo-app-auth;
let userData = {
clientId: 'CLIENT_ID',
additionalParameters: {response_type: 'code', show_dialog: true},
redirectUrl: 'http://www.google.com',
issuer: "https://accouns.spotify.com/authorize"
};
AppAuth.authAsync(userData)
.then( data => console.log(data))
.catch( (error, state) => console.log(`Error: ${error} State: ${state}`));
This is related to the issue at https://github.com/expo/expo/pull/5311 - there is an error in a ternary operator in expo-app-auth which will get rolled into an upcoming release. In the meantime, you must specify a dummy value for registrationEndpoint in your service configuration object.

App runs fine on heroku, except for one problem

I have an application that allows people to look up their government representatives using an address. When it is connected to a local mongo db it works perfectly. I am using the db to store like/dislikes on the reps page. The problem is that I added the documents in the db manually and so it doesn't/cant track likes on heroku, since the db isnt create dynamically in the code. That didn't seem like too much of a problem though. I would just create the exact same documents in Atlas db, right? Well here I am several hours later and Im pulling my hair out.
I have never used Atlas but it seems straightforward. Use their url to connected to your cluster/db. Thats about it from what I can tell. Problem is, I can get an empty array back from a return all route... and thats it. I have two docs in the db currently and need them to track my likes/dislikes. Im just not sure where to go from here and have to deploy this by tomorrow evening. Please help
I have reformatted the url six ways to Sunday. I have moved around code in server to try and diagnose the problem and I have messed with my routes. Ive tried postman and am getting an array back but thats it. I added a console.log in server that returns my cluster so I know Im connected in some capacity.
Sorry if Im missing something obvious and didn't show enough code, I am fairly new to all this if you cant tell. Thank you so much for any help you can provide.
````const routes = require("./routes");
````const app = express();
//////////////////////////////////////////////////////////////////
````const mongoose = require("mongoose")
````const bodyParser = require("body-parser")
````//Bodyparser middleware
````app.use(bodyParser.json());
````//DB config
````const MongoClient = require('mongodb').MongoClient;
````const uri = "mongodb+srv://NicoP93:REDACTED#cluster0-vplwy.mongodb.net/test?retryWrites=true"
````MongoClient.connect(uri, function(err, client) {
````if(err){
````console.log("Not connected to atlas")
````} else {
````console.log("connected to atlas")
````}
````const collection = client.db("Overthrow").collection("Knowledge")
````if(collection){console.log("Collection Connection")};
````console.log(collection);
````mongoose.connect(uri).then(() => {console.log("Mongoose connected")})
````})
````const items = require("./routes/api/items")
````app.use('/api/items', items);
````const PORT = process.env.PORT || 3001;
///////////////////////////////////////////////////////////////////////////////
````app.use(express.urlencoded({
````extended: true
````}));
````app.use(express.json());
````if (process.env.NODE_ENV === "production") {
````app.use(express.static("client/build"));
````}
````app.use(routes);
````app.listen(PORT, function () {
```` console.log("🌎 ==> API Server now listening on port " + PORT);
````});
When I hit the get all route I would expect something like
{"_id:5cd9fff41c9d440000f25cb8
buttonLike:0
buttonDislike:0
representativeName:"Ted Cruz"
__v:"0"}
because thats what in my atlas db, Im looking at it right now in their website.
Additionally, node server returns:
{(node:5600) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
🌎 ==> API Server now listening on port 3001
connected to atlas
Collection Connection
Collection {
s:
{ pkFactory:
{ [Function: ObjectID]
index: 550824,
createPk: [Function: createPk],
createFromTime: [Function: createFromTime],
createFromHexString: [Function: createFromHexString],
isValid: [Function: isValid],
ObjectID: [Circular],
ObjectId: [Circular] },
db:
Db {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
s: [Object],
serverConfig: [Getter],
bufferMaxEntries: [Getter],
databaseName: [Getter] },
topology:
ReplSet {
_events: [Object],
_eventsCount: 25,
_maxListeners: Infinity,
clientInfo: [Object],
s: [Object] },
dbName: 'Overthrow',
options:
{ promiseLibrary: [Function: Promise],
readConcern: undefined,
readPreference: [ReadPreference] },
namespace: 'Overthrow.Knowledge',
readPreference: ReadPreference { mode: 'primary', tags: undefined },
slaveOk: true,
serializeFunctions: undefined,
raw: undefined,
promoteLongs: undefined,
promoteValues: undefined,
promoteBuffers: undefined,
internalHint: null,
collectionHint: null,
name: 'Knowledge',
promiseLibrary: [Function: Promise],
readConcern: undefined,
writeConcern: undefined } }
Mongoose connected }

Resources