Electron webContents.send does not always work - javascript

I use the following to send some data to another window;
try{
win.webContents.once('dom-ready', () => win.webContents.send('send-data', data));
}
catch(err){
console.log("Caught ",err);
}
And for receivig;
ipcRenderer.on('send-data', function (event,data) {
console.log("Loaded ", data);
});
The thing is, the "data" here is sometimes assembled very quickly and it works fine. However, sometimes it takes a while and the other window is already loaded at this point. No data is received in that case, and no error message either. But then I can simply use the following to send it without problems;
win.webContents.send('send-data', data)
I couldn't find a way to apply for both cases. Any suggestions?

The short answer is no.
Electron doesn't have a function to wait for the window to load, then send a message, or send a message right away if the window's already loaded.
However this can be done with some simple code:
var hasWindowLoaded = false;
var hasDataBeenSent = false;
var data = {};
win.webContents.once('dom-ready', () => {
hasWindowLoaded = true;
if (!hasDataBeenSent && data) {
win.webContents.send('send-data', data);
hasDataBeenSent = true;
}
});
// Now add this where you build the `data` variable.
function loadData () {
data = {'sampleData': 'xyz'};
if (!hasDataBeenSent && hasWindowLoaded) {
win.webContents.send('send-data', data);
hasDataBeenSent = true;
}
}
Once the data's loaded in loadData it'll check if the window's finished loading and if it has then it sends the data right away.
Otherwise it stores the data in a varible (data) and once the window loads it sends it to the window.

Another approach that you may want to consider is sending data to the browserWindow using query strings.
const data = { hello: "world" }; // sample data
// send it using query strings
browserWindow.loadFile(YOUR_HTML_FILE_PATH, {
query: { data: JSON.stringify(data) },
});
// parse this data in the browserWindow
const querystring = require("querystring");
const query = querystring.parse(global.location.search);
const data = JSON.parse(query["?data"]);
console.log(data); // { hello: "world" }

Related

How do I only render the updated stat - websockets

right now the entire div re-renders, but I am searching for a way to only re-render the updated statistic
these are parts of what I have now
updatestats.js
document.querySelector("#submit").addEventListener("click", function (e) {
let country = document.querySelector("#country").value
let numberCases = document.querySelector("#number").value
fetch(base_url + "/api/v1/stats/updatestats", {
method: "put",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"country": country,
"numberCases": numberCases
})
}).catch(err => {
console.log(err);
})
primus.write({ "action": "update" })
stats.js
primus.on("data", (json) => {
if (json.action === "update") {
document.querySelector("#overview").innerHTML = ""
appendInfo()
}
})
function appendInfo() {
fetch(base_url + "/api/v1/stats", {
method: "get",
headers: {
'Content-Type': 'application/json'
},
}).then(response => {
return response.json();
}).then(json => {
json.data.stats.forEach(stat => {
let country = stat.country
let numberCases = stat.numberCases
let p = document.createElement('p')
let text = document.createTextNode(`${country}: ${numberCases}`)
p.appendChild(text)
let overview = document.querySelector("#overview")
overview.appendChild(p)
})
}).catch(err => {
console.log(err);
})
}
window.onload = appendInfo();
stats.pug
body
h1 Cases by country
div#overview
So if I only update the country Belgium I only want that statistic to be changed. Now everything seems to reload
What I meant with my suggestion is to keep te communication of data between client and server strictly in the sockets. Meaning when one user updates 1 value on their end, that value will be send to the server and stored. After the server finished storing the value, that same value will be sent to all other clients. This way you only send and receive the parts that have been changed without having to download everything on every change.
I might not be able to write the code exactly as it should be as I have limited experience with Primus.js and know little about your backend.
But I would think that your frontend part would look something like this. In the example below I've removed the fetch function from the click event. Instead send the changed data to the server which should handle those expensive tasks.
const countryElement = document.querySelector("#country");
const numberCasesElement = document.querySelector("#number");
const submitButton = document.querySelector("#submit");
submitButton.addEventListener("click", function (e) {
let data = {
action: 'update',
country: countryElement.value,
numberCases: numberCasesElement.value
};
primus.write(data);
});
Now the server should get a message that one of the clients has updated some of the data. And should do something with that data, like storing it and letting the other clients know that this piece of data has been updated.
primus.on('data', data => {
const { action } = data;
if (action === 'update') {
// Function that saves the data that the socket received.
// saveData(data) for example.
// Send changed data to all clients.
primus.write(data);
}
});
The server should now have stored the changes and broadcasted the change to all other clients. Now you yourself and other will receive the data that has been changed and can now render it. So back to the frontend. We do the same trick as on the server by listening for the data event and check the action in the data object to figure out what to do.
You'll need a way to figure out how to target the elements which you want to change, you could do this by having id attributes on your elements that correspond with the data. So for example you want to change the 'Belgium' paragraph then it would come in handy if there is a way to recognize it. I won't go into that too much but just create something simple which might do the trick.
In the HTML example below I've given the paragraph an id. This id is the same as the country value that you want to update. This is a unique identifier to find the element that you want to update. Or even create if it is not there.
The JavaScript example after that receives the data from the server through the sockets and checks the action. This is the same data that we send to the server, but only now when everybody received we do something with it.
I've written a function that will update the data in your HTML. It will check for an element with the id that matches the country and updates the textContent property accordingly. This is almost the same as using document.createTextNode but with less steps.
<div id="overview">
<p id="Belgium">Belgium: 120</p>
</div>
const overview = document.querySelector("#overview");
primus.on('data', data => {
const { action } = data;
if (action === 'update') {
updateInfo(data);
}
});
function updateInfo(data) {
const { country, numberCases } = data;
// Select existing element with country data.
const countryElement = overview.querySelector(`#${country}`);
// Check if the element is already there, if not, then create it.
// Otherwise update the values.
if (countryElement === null) {
const paragraph = document.createElement('p');
paragraph.id = country;
paragraph.textContent = `${country}: ${numberCases}`;
overview.append(paragraph);
} else {
countryElement.textContent = `${country}: ${numberCases}`;
}
}
I hope that this is what you are looking for and / or is helpful for what you are trying to create. I want to say again that this is an example of how it could work and has not been tested on my end.
If you have any questions or I have been unclear, then please don't hesitate to ask.
To elaborate #EmielZuurbier's suggestion in the comment, please try the following code.
//Client-side
primus.emit('data',data);
primus.on("dataUpdated", (json) => {
});
//Server-side
primus.on('data',data =>{
//process it here and then
//send it out again
primus.emit('dataUpdated','the data you want to send to the front end');
})

How to edit request url in service worker?

I'm using cache first caching strategy for my pwa, for every GET request I first look if that request exists in cache, if it does I return it and update the cache.
The problem is that users can switch between multiple projects, so when they switch to another project,
the first time they open some url, they get the stuff from previous project if it exists in cache.
My solution is to try to add GET parametar ?project=projectId(project=2 for example) in the service worker, so each project would have its own version of the request saved in the cache.
I wanted to concatinate project id to the event.request.url, but I've read here that it is read only.
After doing that, hopefully I would have urls like this in cache:
Instead of: https://stackoverflow.com/questions
I would have: https://stackoverflow.com/questions?project=1
And: https://stackoverflow.com/questions?project=2
So I would get questions from the project I'm on, instead of just getting questions from previous project is /questions is saved in cache already.
Is there a way to edit request url in service worker?
My service worker code:
self.addEventListener('fetch', function(event) {
const url = new URL(event.request.clone().url);
if (event.request.clone().method === 'POST') {
// update project id in service worker when it's changed
if(url.pathname.indexOf('/project/') != -1 ) {
// update user data on project switch
let splitUrl = url.pathname.split('/');
if (splitUrl[2] && !isNaN(splitUrl[2])) {
console.log( user );
setTimeout(function() {
fetchUserData();
console.log( user );
}, 1000);
}
}
// do other unrelated stuff to post requests
.....
} else { // HANDLE GET REQUESTS
// ideally,here I would be able to do something like this:
if(user.project_id !== 'undefined') {
event.request.url = event.request.url + '?project=' + user.project_id;
}
event.respondWith(async function () {
const cache = await caches.open('CACHE_NAME')
const cachedResponsePromise = await cache.match(event.request.clone())
const networkResponsePromise = fetch(event.request.clone())
if (event.request.clone().url.startsWith(self.location.origin)) {
event.waitUntil(async function () {
const networkResponse = await networkResponsePromise.catch(function(err) {
console.log( 'CACHE' );
// return caches.match(event.request);
return caches.match(event.request).then(function(result) {
// If no match, result will be undefined
if (result) {
return result;
} else {
return caches.open('static_cache')
.then((cache) => {
return caches.match('/offline.html');
});
}
});
});
await cache.put(event.request.clone(), networkResponse.clone())
}())
}
// news and single photos should be network first
if (url.pathname.indexOf("news") > -1 || url.pathname.indexOf("/photos/") > -1) {
return networkResponsePromise || cachedResponsePromise;
}
return cachedResponsePromise || networkResponsePromise;
}())
}
});
It's possible to use any URL as a cache key when reading/writing to the Cache Storage API. When writing to the cache via put(), for instance, you can pass in a string representing the URL you'd like to use as the first parameter:
// You're currently using:
await cache.put(event.request.clone(), networkResponse.clone())
// Instead, you could use:
await cache.put(event.request.url + '?project=' + someProjectId, networkResponse.clone())
But I think a better approach that would accomplish what you're after is to use different cache names for each project, and then within each of those differently-named caches you would not have to worry about modifying the cache keys to avoid collisions.
// You're currently using:
const cache = await caches.open('CACHE_NAME')
// Instead, you could use:
const cache = await caches.open('CACHE_NAME' + someProjectId)
(I'm assuming that you have some reliable way of figuring out what the correct someProjectId value should be inside of the service worker, based on which client is making the incoming request.)

How to pass data between pageFunction executions in Apify web

I'm scraping website with Apify. I want to scrape different types of pages and then combine the data into one data set. Now i have different sets of data for each kind of pages (users, shots). How to transfer data between pageFunction executions, ex. to calculate followers number for each shot author.
async function pageFunction(context) {
const { request, log, jQuery } = context;
const $ = jQuery;
if (request.url.indexOf('/shots/') > 0) {
const title = $('.shot-title').text();
return {
url: request.url,
title
};
} else if (request.userData.label === "USER") {
var followers_count = $('.followers .count').first().text();
return {
url: request.url,
followers_count
};
}
}
If I understand the question correctly, you can pass the data through crawled pages and save only one item in the end. For this use case, you can work with userData, which you can pass with every request.
For example, if you would like to pass the data from /shots site to the USER, you could do it like this. (but it requires you to enqueue pages manually to control the flow of the data, also this approach except that the /shots type of the page is the first one you visit and then continue)
async function pageFunction(context) {
const { request, log, jQuery } = context;
const $ = jQuery;
if (request.url.indexOf('/shots/') > 0) {
const title = $('.shot-title').text();
const userLink = 'some valid url to user page'
//add to the queue your request with the title in the userData
await context.enqueueRequest({
url: userLink,
userData:{
label:'USER',
shotsTitle: title
}
})
} else if (request.userData.label === "USER") {
var followers_count = $('.followers .count').first().text();
//here you need to get the shotsTitle and return it
return {
url: request.url,
followers_count,
shotsTitle: request.userData.shotsTitle
};
}
}
If you would need to share the between runs of the actors, that is other topic, let me know if it helped.
Also would recommend going through the getting started guide which is here.

Cannot access a key value pair of a changed object

Please excuse my code
From an external source , I am given the following external data which I name loxAP3
to which I am trying to firstly retrieve svg data related to the rooms.image property and then change the incoming svg data to work with react, using the following code.
createRoomData(loxAPP3, socket) {
console.log(loxAPP3)
let rooms = []
let rawRooms = loxAPP3.rooms
for (const rawRoom in rawRooms) {
rooms.push(rawRooms[rawRoom])
}
//add svg property with blank property value
rooms.forEach((room) => {
room.svg = ''
})
//fetch image data for each room in loxApp3.rooms
rooms.forEach((room) => {
const image = room.image
socket
.send(image)
.then(function(respons) {
//console.log("Successfully fetched svg image " + respons ); // success
room.svg = respons
//console.log(room.svg) // success console returns room.svg data
},
function(err) {
console.error(err);
}
);
})
this.setState({
rooms: rooms
}, () => {
console.log(rooms) // success rooms[0].svg is shown as having been populated
this.adjustSvGImageToReact()
})
}
console.log(rooms) // success rooms[0].svg is shown as having been populated
However the problem comes when I try and manipulate the room object, if I log a property that already existed from the original data, there is no problem, however if I try an fetch the .svg property it comes back not as undefined but as the empty string I first set it to be.
adjustSvGImageToReact() {
this.state.rooms.forEach((room)=>{
console.log(room.name) // success
console.log(room.uuid) // success
console.log(room.svg) //empty
})
}
Create an array of the socket.send() promises instead of calling them inside forEach
Then you can use Promise.all() to set the state and call adjustSvGImageToReact() after the socket requests have completed
const svgPromises = rooms.map((room) => {
const image = room.image
return socket
.send(image)
.then((respons)=> room.svg = respons)
})
Promise.all(svgPromises).then(() => {
this.setState({rooms: rooms}, () => {
console.log(rooms) // success rooms[0].svg is shown as having been populated
this.adjustSvGImageToReact()
});
}).catch(err=>console.log('One of the socket requests failed'))

Trying to use a module pattern to safely get data

I'm attempting to not have to use a global variable called 'data'
In my js file I now have this function:
var Module_BarDataDaily = (function() {
var data;
d3.csv("myData.csv", function(rows) {
data = rows;
});
return {
dataX: data
}
})();
I was then (probably wrongly) under the impression that I can access this data via Module_BarDataDaily.dataX - so in a subsequent function I do the following:
function TOPbarChart(
grp, meas, colorChosen) {
TOPbarData = Module_BarDataDaily.dataX.map(function(d) { //line 900
return { ...
Console then just gives me an exception on line 900 of the following:
TypeError: Module_BarDataDaily.dataX is undefined
What am I doing wrong & how do I fix it?
The issue here is that d3.csv is asynchronous, so data is filled at a different time than accessed.
If you want to run d3.csv once, get the data and save them elsewhere, you can try something like this or this or this
In general:
// define your module here
var Module_BarDataDaily = function(data) {
this.dataX = data; //this.dataX will store the data
this.process = function(){// do whatever you need afterwards
console.log(this.dataX);
}
};
var process = function(myModule){
// or use another function to do what you need etc
console.log(myModule.dataX);
}
// load your csv once and save the data
d3.csv("path/to/your.csv", function(error, data) {
// after some proper error handling...
var myModule = new Module_BarDataDaily(data);
// so then you can do anything after that and
// your myModule will have data without calling d3.csv again
myModule.process();
//or
process(myModule);
});
Hope this helps!
Good luck!
I've used the following based on mkaran's answer:
var Module_BarDataDaily = function(data) {
this.dataX = data;
};
d3.csv("data/BarChart_data.csv", function(error, rows) {
var myModule = new Module_BarDataDaily(rows);
var chart = barChart();
chart.render(myModule.dataX);
d3.selectAll('input[name="meas"]').on("change", function change() {
chart.currentMeasure(this.value)
chart.render(myModule.dataX);
});
});

Categories