Vuex blocking/freezing on websocket updates - javascript

i didn't find a better way to word the title, sorry.
I'm building a table of crypto markets. (Vue/Vuetify v-data-table). Data is kept in a Vuex store.
I'm connecting to the (Laravel/Echo) API via Vue-Native-Websocket
Vue.use(VueNativeSock, url, {
format: 'json',
store: store,
connectManually: true,
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 3000,
})
After opening the page in question, i'm subscribing to the update method
subscribeMarketUpdates() {
let marketsToSubscribe = []
const data = this.$store.state.exchange.markets
const marketKeys = Object.keys(data)
for (let i = 0; i < marketKeys.length; i++) {
marketsToSubscribe = marketsToSubscribe.concat(data[marketKeys[i]].map(market => market.name))
}
this.$store.dispatch('sendExchangeSocketMessage', {
"method": "state.subscribe",
"params": marketsToSubscribe, // <- all currently present markets
"id": Date.now(),
})
},
then i get back a success message, followed by the requested market update messages (one update message PER MARKET (!) ).
i handle them as follows:
SOCKET_ONMESSAGE (state, message) {
state.exchangeSocket.message = message
if (message.method === 'state.update') {
this.commit('exchange/UPDATE_MARKET_STATE', { params: message.params, BigNumber })
}
}
UPDATE_MARKET_STATE(state, payload) {
console.log('updating market ', params[0])
let { params, BigNumber } = payload
let coin = params[0].match(/(^.*)-/)[1]
let market = params[0].match(/\w+$/)[0]
let updatedValues = params[1]
let resultIndex = state.markets[market].findIndex((e) => e.ticker === coin)
let result = state.markets[market][resultIndex]
result.amount = new BigNumber(updatedValues.amount).toFixed(8)
result.change = new BigNumber(updatedValues.change).toFixed(8)
result.change_pct = new BigNumber(updatedValues.change_pct).toFixed(4)
result.close = new BigNumber(updatedValues.close).toFixed(8)
result.high = new BigNumber(updatedValues.high).toFixed(8)
result.last = new BigNumber(updatedValues.last).toFixed(8)
result.low = new BigNumber(updatedValues.low).toFixed(8)
result.open = new BigNumber(updatedValues.open).toFixed(8)
result.period = updatedValues.period
result.volume = updatedValues.volume
state.markets[market][resultIndex] = result
}
}
it kind of works, but i noticed it being painfully slow.
a console.time around the commit call reveals 400-700ms (!) for each execution.
each line inside UPDATE_MARKET_STATE reports 0.02ms though.
at first i thought something was wrong on the backend, but the guy working on that ensured me that all of the messages are sent within a couple of ms. there has to be something here on my end, that i'm not understanding. this is the backend log:
[2022-02-24 14:47:38.733082] [29576] [trace]aw_state.c:206(on_timer): send request to 127.0.0.1:1234, cmd: 301, sequence: 2544786, params: ["ETH-BTC", 86400]
[2022-02-24 14:47:38.733096] [29576] [trace]aw_state.c:206(on_timer): send request to 127.0.0.1:1234, cmd: 301, sequence: 2544787, params: ["404-BTC", 86400]
[2022-02-24 14:47:38.733108] [29576] [trace]aw_state.c:206(on_timer): send request to 127.0.0.1:1234, cmd: 301, sequence: 2544788, params: ["2GIVE-BTC", 86400]
[2022-02-24 14:47:38.733121] [29576] [trace]aw_state.c:206(on_timer): send request to 127.0.0.1:1234, cmd: 301, sequence: 2544789, params: ["$PAC-BTC", 86400]
[2022-02-24 14:47:38.733132] [29576] [trace]aw_state.c:206(on_timer): send request to 127.0.0.1:1234, cmd: 301, sequence: 2544790, params: ["1337-BTC", 86400]
(note all of the messages being sent nearly instantly)
the table for displaying the data seems to lock up too (i can only scroll the table each second, and it coincides with a console.log i made, so it really seems there is something blocking, even though i can't see anything that could be the culprit).
17:36:03.863 actions.js?4221:19 updating market 1337-BTC
17:36:04.646 actions.js?4221:19 updating market 2GIVE-BTC
17:36:05.421 actions.js?4221:19 updating market 2X2-BTC
17:36:06.264 actions.js?4221:19 updating market 404-BTC
17:36:07.030 actions.js?4221:19 updating market ABJ-BTC
17:36:07.795 actions.js?4221:19 updating market ACP-BTC
17:36:08.520 actions.js?4221:19 updating market ADC-BTC
17:36:09.415 actions.js?4221:19 updating market AERM-BTC
17:36:10.170 actions.js?4221:19 updating market ALEX-BTC
17:36:10.977 actions.js?4221:19 updating market AMBER-BTC
(note the update being called roughly each second, even though the ws messages are coming in much faster)
so my assumption is: there's something wrong in how i update my vuex store. something is blocking there, but i can't figure out what exactly. i don't see anything there that could possible cause this lock-up.
(i've also tried to handle the updates in an action (and calling the mutation from there) rather than a mutation, the result was the same, so here you see the mutation code)
i hope somebody can spot my error, because i can't :/
thanks for any help

Related

why messaging().sendtodevice is not working sometimes?

I'm using the following code to send a notification from one device to another using FCM. Everything works fine until before return admin.messaging().sendToDevice(...). The 'Token ID: ' log displays token ID of the receiver, but when I set the variable token_id to the sendToDevice function, the notification is not called, therefore the notification is not sent. Can someone tell me what's wrong?
var firebase = require("firebase-admin");
var serviceAccount = require("./julla-tutorial.json");
console.log("enter in then Firebase Api");
const firebaseToken = [
'e0T6j1AiRjaa7IXweJniJq:APA91bHNznSHSIey08s-C-c3gchci6wepvhP1QxQyYbmZ8LySI3wnu64iW7Q23GhA6VCdc4yodZoCFOgynfAb5C8O8VE81OcSv_LL-K3ET1IKGZ_6h35n-_q5EKFtfJWlzOqZr4IvpiB',
'dNWnSqyCQbufzv1JutNEWr:APA91bFcI9FDyRxHRBEcdw4791X0e-V0k1FjXcSstUA67l94hSojMRCd6LWr2b57azNEt3z_XLwLljMX4u2mc9cZDrAVm55Mw9CHGyue-09KofWnnHNR9XWBibc4T76xOV_DWX7T2RvW',
'cq65rtuaTCKGk5lHk7UabN:APA91bFR3kAArg6lhuBq7ktNuBk7Z9MXXk3PskqhYa8CgNaEl6MX4TQ5lo35d6XhnCQ4fEkCkyZ_j08evxE9Y4oVCRTEdqsrkccCVTE8Di47lfmDR3i1NdoL3re9oLw6F_uNsnvRoQcq'
]
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount)
})
const payload = {
notification: {
title: 'Demo 2345',
body: 'dfghj',
sound: 'default',
color: 'yellow',
android_channel_id: 'default',
channel_id: 'default'
},
data: { id: 'broadcast', channelId: 'default' }
}
const options = {
priority: 'high',
timeToLive: 60 * 60 * 24, // 1 day
};
console.log('------payload---',payload);
console.log('-----TOKEN_Array----',firebaseToken);
console.log('-------options-----',options);
firebase.messaging().sendToDevice(firebaseToken, payload, options).then(function (response) {
console.log('--------response',response);
}) .catch(function (error) {
console.log('-------rejet',reject);
});
It looks like you did not change the code from this tutorial:
https://medium.com/#jullainc/firebase-push-notifications-to-mobile-devices-using-nodejs-7d514e10dd4
you will need to change the 2nd line of code:
var serviceAccount = require("./julla-tutorial.json");
to actually point to your own firebase-push-admin.json file which holds your private keys registering your backend app with the firebase cloud messaging api. you can download this file from the firebase console as mentioned in the above article.
I recommend hiding this file from your git history by adding it to .gitignore so you dont accidentally push your private keys to a public repo.
I will link you another resource in addition to above link which helped me implement firebase push notifications in a nodeJS backend app.
https://izaanjahangir.medium.com/setting-schedule-push-notification-using-node-js-and-mongodb-95f73c00fc2e
https://github.com/izaanjahangir/schedule-push-notification-nodejs
Further I will also link you another repo where I am currently working on a fully functional firebase push notification implementation. Maybe it helps to actually see some example code.
https://gitlab.com/fiehra/plants-backend

Javascript: Can't send updated object values to Node/Express

I'm building a web app that should fetch specific stock data for the user's chosen stock from Yahoo Finance and then send this data to a Node/Express server.
PROBLEM: Everything works, except that what is received by Node/Express are the original null-values in the JavaScript object and not the values received from Yahoo Finance.
The initial object variable definitions are first made in index.js:
const stockData = {
ticker: "",
name: "",
dateAdd: 0,
priceDateAdd: 0,
priceNow: 0,
movement: 0
};
The post method is defined:
const options = {
method: 'POST',
body: JSON.stringify(stockData),
headers: {
"Content-Type": "application/json"
}
Frontend, the user now chooses a ticker, and a request is sent to Yahoo Finance API to get real values for the stockData object for that ticker. No problems there. E.g. for stockData.priceDateAdd the browser console returns the value 67 for a sample stock.
A function now runs to send the fetched values from Yahoo Finance to Node/Express:
async function sendData() {
const response = await fetch('/add', options);
const serverData = await response.json();
console.log(serverData);
BUT ... the response from Node/Express shows the original null-values of the object, not the ones fetched from Yahho Finance, i.e., this is the response:
{
ticker: '',
name: '',
dateAdd: 0,
priceDateAdd: 0,
priceNow: 0,
movement: 0
}
I run a second browser console.log after the response from the server, and this continues to show the correct value of stockData.priceDateAdd as 67.
But for some reason the server receives the original null-values. Have spent hours and can't figure it out. Here's hoping for help.
After more trial and error I have answered my own question:
I moved the const definition inside the function that is being called to send the data, and this worked. So the code is now:
async function sendData() {
const options = {
method: 'POST',
body: JSON.stringify(stockData),
headers: {
"Content-Type": "application/json"
}
};
const response = await fetch('/add', options);
const serverData = await response.json();
console.log(serverData);
}

How to perform subquery when rendering json data with React?

Edit: Moved the getMetadata() call out of render, as suggested by #Robin Zigmond. Still having trouble getting the proper value from getMetadata() returned, but that's different from my original question. Updating code just for reference.
Quick background: I have a fair amount of experience with shell scripting and stuff like Perl and PHP, but javascript and especially the libraries on top of it like React feel very foreign to me. Forcing myself to develop this with React to teach myself something new, so if you see any bad practices, feel free to suggest improvements!
That said, I'm trying to write a simple app that:
Prompts for search parameter
Runs query against 3rd party service that returns json results
For each returned element, run additional query to get more details
Display tabular results
I have 1, 2, and 4 generally worked out, but struggling with 3. Here's what I have so far, with less important code snipped out:
class VGSC_Search extends React.Component {
constructor() {
super();
this.state = {
submitted: false,
platform: '',
games: [],
metadata: [],
files: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({platform: event.target.value});
}
handleSubmit(event) {
this.setState({submitted: true});
<SNIP - vars>
fetch(encodeURI(searchurl + query + fields + sort + rows))
.then(result => result.json())
.then(data => this.setState({
games: data.response.docs.map(game => ({
identifier: game.identifier,
title: game.title,
creator: game.creator,
year: game.year,
uploader: this.getMetadata(game.identifier),
}))
}));
event.preventDefault();
}
getMetadata(id) {
<SNIP - vars>
fetch(encodeURI(metadataurl + id + metadatainfo))
.then(result => result.json())
.then(data => this.setState({metadata: data.response}));
}
renderResults() {
const {games} = this.state;
const {metadata} = this.state;
return (
<SNIP - table header>
<tbody>{games.map(game =>
<tr key={game.identifier}>
<td><a href={'https://archive.org/details/' + game.identifier}>{game.title}</a></td>
<td>{game.creator}</td>
<td>{game.year}</td>
<td>{game.uploader}</td>
</tr>
)}</tbody>
</table>
);
}
render(){
return (
<div>
<SNIP - form>
<br/>
{this.state.submitted && this.renderResults()}
</div>
);
}
}
My problem is with that Uploader field, which should run getMetadata() on the given identifier and return the name of the uploader. I'm having trouble figuring out how to reference the result, but my biggest problem is actually that my browser keeps running getMetadata() on all displayed items in an endless loop. Eg, from the Developer Tools log:
XHR GET https://archive.org/metadata/4WheelThunderEuropePromoDiscLabel/metadata/uploader [HTTP/1.1 200 OK 105ms]
XHR GET https://archive.org/metadata/AirJapanCompleteArtScans/metadata/uploader [HTTP/1.1 200 OK 279ms]
XHR GET https://archive.org/metadata/AeroWings-CompleteScans/metadata/uploader [HTTP/1.1 200 OK 287ms]
XHR GET https://archive.org/metadata/BioCodeVeronicaLEDCT1210MNTSCJ/metadata/uploader [HTTP/1.1 200 OK 279ms]
XHR GET https://archive.org/metadata/Biohazard2ValuePlusDreamcastT1214MNTSCJ/metadata/uploader [HTTP/1.1 200 OK 282ms]
XHR GET https://archive.org/metadata/4WheelThunderEuropePromoDiscLabel/metadata/uploader [HTTP/1.1 200 OK 120ms]
XHR GET https://archive.org/metadata/AirJapanCompleteArtScans/metadata/uploader [HTTP/1.1 200 OK 120ms]
<SNIP>
The first search returns 5 results, and getMetadata() is run correctly on those five results, but note that it starts repeating. It'll do that endlessly until I reload the page.
I'm guessing that has something to do with running getMetadata() inside a function that's being rendered, but I'm not sure why, and having trouble thinking of a good alternate way to do that.
Can anyone explain why I'm seeing this behavior, and (hopefully) offer a suggestion on how to properly implement this?
Thanks!
The infinite loop happens because you're running getMetadata inside render (or rather inside a function that's called from render) - and that results in changing state, which causes a rerender, and so on in an endless loop.
It's bad practice in any situation to call any function which cause any "side effects" from within render - render should simply determine the output given the component's props and state. Typically data-fetching like you're doing is done inside componentDidMount and/or componentDidUpdate. However in this case, where you appear to need to fetch additional data based on the first response in handleSubmit, it seems that you need to call getMetadata from within the final .then callback of that function.
After your latest edit, I can see the problem with the approach you tried here. this.getMetadata doesn't actually return anything. To fix it, you can return the Promise returned by fetch:
getMetadata(id) {
<SNIP - vars>
return fetch(encodeURI(metadataurl + id + metadatainfo))
.then(result => result.json())
.then(data => this.setState({metadata: data.response}));
}
and then use asyc/await inside handleSubmit, with Promise.all to manage the array:
fetch(encodeURI(searchurl + query + fields + sort + rows))
.then(result => result.json())
.then(async data => {
const games = await Promise.all(data.response.docs.map(async game => ({
identifier: game.identifier,
title: game.title,
creator: game.creator,
year: game.year,
uploader: await this.getMetadata(game.identifier),
})));
this.setState({ games });
});

Getting PUT routes to work in Angular

I'm seeking some wisdom from the Angular community. I am working on a simple project using the MEAN stack. I have set up my back-end api and everything is working as expected. Using Postman, I observe expected behavior for both a GET and PUT routes to retrieve/update a single value - a high score - which is saved in it's own document in its own collection in a MongoDB. So far so good.
Where things go off track is when trying to access the PUT api endpoint from within Angular. Accessing the GET endpoint is no problem, and retrieving and displaying data works smoothly. However, after considerable reading and searching, I am stll unable to properly access the PUT endpoint and update the high score data when that event is triggered by gameplay. Below are the snippets of code that I believe to be relevant for reference.
BACK-END CODE:
SCHEMA:
const _scoreSchema = {
name: { type: String, required: true },
value: { type: Number, "default": 0 }
};
ROUTES:
router
.route('/api/score/:highScore')
.put(scoreController.setHighScore);
CONTROLLER:
static setHighScore(req, res) {
scoreDAO
.setHighScore(req.params.highScore)
.then(highScore => res.status(200).json(highScore))
.catch(error => res.status(400).json(error));
}
DAO:
scoreSchema.statics.setHighScore = (value) => {
return new Promise((resolve, reject) => {
score
.findOneAndUpdate(
{"name": "highScore"},
{$set: {"value": value} }
)
.exec(function(err, response) {
err ? reject(err)
: resolve(response);
});
});
}
ANGULAR CODE:
CONTROLLER:
private _updateHighScore(newHighScore): void {
console.log('score to be updated to:', newHighScore)
this._gameService
.updateHighScore(newHighScore);
}
SERVICE:
updateHighScore(newHighScore: Number): Observable<any> {
console.log(newHighScore);
let url = '/api/score/' + newHighScore;
let _scoreStringified = JSON.stringify({value: newHighScore});
let headers = new Headers();
headers.append("Content-Type", "application/json");
return this._http
.put(url , _scoreStringified, {headers})
.map((r) => r.json());
}
Note that the console.log(newHighScore) in the last block of code above correctly prints the value of the new high score to be updated, it's just not being written to the database.
The conceptual question with PUT routes in angular is this: If the api is already set up such that it receives all the information it needs to successfully update the database (via the route param) why is it required to supply all of this information again in the Angular .put() function? It seems like reinventing the wheel and not really utilizing the robust api endpoint that was already created. Said differently, before digging into the docs, I naively was expecting something like .put(url) to be all that was required to call the api, so what is the missing link in my logic?
Thanks!

File upload blocking with bigger files

Intro
What I'm trying to achieve is a simple file upload with a progress indication with redux-saga and react). I'm having problems getting this indication because the file upload seems the be blocking - which it shouldn't be.
Expected behaviour
before the file upload starts a re render is triggered and the spinner is shown and the window is not blocked.
Current behaviour
What I have at the moment is a component with a table that show a file per row. A optimistic row gets added with a spinner as the content when the users uploads a file. As soon as the file is uploaded the optimistic row will be replaced by a real row with the file's name etc. When I'm uploading a file around 50MB the window gets blocked and shortly before the file is uploaded (around 0.5s before) the spinner appears and then the file is already uploaded and the spinner disappears again.
side notes
If you replace the file upload with new Promise(res => setTimeout(res, 5000)) it all works fine => it seems like there is a problem with the xhr / fetch.
I've implemented the same using XHR, promises and an onProgress callback to make sure the problem is not fetch.
the implementation looks very close to: https://gist.github.com/robinfehr/2f4018259bf026a468cc31100fed5c9f
Also with this implementation I've experienced the same issue - blocking until almost the end of the upload.
If I put log statements into the render function of the component to see if it's getting re rendered before the file is uploaded, I see (as soon as the block stops and the file is uploaded) that the log statements in the render function are actually correctly triggered with a timestamp before the file upload was done.
In this implementation I'm using the same reducer: optimistic event as well as the real event that reverts the optimistic event, they go trough the same reducer (named fileReducer here).
using a second reducer and concatination instead of the optimistic revert logic helps to displaying the spinner earlier but does not help with the blocking. It therefore seems like the middleware also gets blocked by the blocking call.
saga: (postData uses fetch)
function* createDocument(partnerId, { payload, meta }) {
const siteId = getSiteIdFromRoute();
const {
mediaGroupId,
customArticleId,
logicalComponentId,
type,
name,
documentSrc,
meta: metaFromFrontEnd
} = payload;
const commonEventId = uuid();
const hans = {
optimistic: true
};
const payloadBasic = {
id: commonEventId,
version: 0,
aggregate: {
id: uuid(),
name: 'document'
},
context: {
name: 'contentManagement'
},
payload: {
name,
type,
links: {
partnerId,
siteId,
logicalComponentId,
customArticleId,
mediaGroupId
}
}
};
// creates the optimistic (fake) row with a spinner in the file list component - action marked as optimistic which will be reverted.
yield put(actions.denormalizeEvent({
...payloadBasic,
name: 'documentCreated',
optimistic: true,
payload: {
...payloadBasic.payload,
uploading: true
}
}));
yield fork(executeDocumentUpload, type, siteId, partnerId, documentSrc, payloadBasic);
}
function* executeDocumentUpload(type, siteId, partnerId, documentSrc, payloadBasic) {
const req = yield call(uploadDocument, type, siteId, partnerId, documentSrc);
const body = yield req.json();
const { meta: metaFromFileUpload, id } = body.response;
// removes the optimistic (fake) row from the file list component and and adds the real row with more file information (optimistic event gets reverted in middleware)
yield put(actions.sendCommandSuccess({
...payloadBasic,
name: 'createDocument',
payload: {
...payloadBasic.payload,
meta: metaFromFileUpload
}
}));
}
function uploadDocument(type, siteId, partnerId, documentSrc) {
let url;
if (type === 'site' || type === 'mediaGroup' || type === 'logicalComponent') {
url = `/file/site/${siteId}/document`;
} else if (type === 'customArticle') {
url = `/file/partner/${partnerId}/document`;
}
return postData(url, documentSrc);
}
The problem was that I did send the file as a base64 encode string and set up the request with the wrong content-type.
'Content-Type': 'text/plain;charset=UTF-8'
putting the file into a FormData object and send the request without the mentioned content-type lead to a non-blocking request.

Categories

Resources