node serialport module not working with child process fork() - javascript

I'm trying to do an asynchronous read in usb with fork() from the child_process module in electron. That is to say, when I click a div (id="read") a message is sent from the renderer process to the main process, which then in response calls fork() and reads in data from USB asynchronously. To handle usb communications I'm using the node-serialport library.
index.html
<html>
<head>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="scroll.css">
<link href="https://fonts.googleapis.com/css?family=Questrial" rel="stylesheet">
</head>
<body>
<div id="ui-container">
<div id="top-bar" class="shadowed">
<div id="top-bar-left-decoration"></div>
<div id="close-app" class="top-bar-button"><div class="unselectable top-button-text">x</div></div>
<div id="maximize-app" class="top-bar-button"><div class="unselectable top-button-text">&#9633</div></div>
<div id="minimize-app" class="top-bar-button"><div class="unselectable top-button-text">-</div></div>
</div>
<div id="navigation-bar">
<div id="deviceSelection" class ="unselectable navigation-button">
<img id="deviceSelection-image" class="navigation-image" src="Icons/selectDevice2.png">
</div>
<div id="info" class="unselectable navigation-button">
<img id="info-image" class="navigation-image" src="Icons/DeviceInfoNoBorder.png">
</div>
<div id="config" class=" unselectable navigation-button">
<img id="config-image" class="navigation-image" src="Icons/DeviceConfigNoBorder.png">
</div>
<div id="graph" class="unselectable navigation-button">
<img id="graph-image" class="navigation-image" src="Icons/DataViewNoBorder.png">
</div>
<div id="export" class="unselectable navigation-button">
<img id="export-image" class="navigation-image" src="Icons/Export.png">
</div>
<div id="options-container"><img id="options" src="Icons/options.png"></div>
</div>
<div id="information-data-view" class="unselectable">
<div id="data">
<div id="push-to-read"></div>
</div>
</div>
</div>
</body>
<script src="ipcRenderer.js" type="text/javascript"></script>
</html>
This is my index.js, responsible for creating the browser window
const electron = require('electron')
const ipcMain = require('electron').ipcMain
const url = require('url')
const path = require('path')
const child_process = require('child_process')
const app = electron.app
var windowFullScreen = false;
const BrowserWindow = electron.BrowserWindow
var MainWindow;
app.on('ready', function()
{
MainWindow = new BrowserWindow({
width: 1024,
height: 768,
backgroundColor : '123355',
frame: false,
resizable: true,
movable: true,
show: false
})
MainWindow.on('ready-to-show', () => {
MainWindow.show()
console.log('Ready to go!')
})
MainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
ipcMain.on("minimize", (event, arg) => {
MainWindow.minimize()
console.log("minimize")
})
ipcMain.on("maximize", (event, arg) => {
if (!windowFullScreen) {
MainWindow.maximize()
windowFullScreen = true;
}
else {
MainWindow.setSize(800, 600)
windowFullScreen = false;
}
console.log("maximize")
})
ipcMain.on("close", (event, arg) => {
MainWindow.close()
console.log("close")
})
ipcMain.on("read", (event, arg) => {
setInterval(()=>{console.log(usbcomm.read(1))}, 1)
const process = child_process.fork('./readUSB.js')
})
})
ipcRenderer.js
This is where I set up onclick event handlers for the DOM. The relevant part here is read.onclick() which sends "read" message to the main process telling it fork the process and run readUSB.js which I've included below.
const electron = require('electron');
const ipcRenderer = electron.ipcRenderer;
var minimize = document.getElementById("minimize-app");
var maximize = document.getElementById("maximize-app");
var read = document.getElementById("push-to-read");
var close = document.getElementById("close-app");
minimize.onclick = (event) => {
ipcRenderer.send("minimize", "an-argument")
console.log("minimize");
}
maximize.onclick = (event) => {
ipcRenderer.send("maximize", "an-argument")
console.log("maximize")
}
close.onclick = (event) => {
ipcRenderer.send("close", "an-argument")
console.log("close")
}
read.onclick = (event) => {
ipcRenderer.send("read", "an-argument")
console.log("read")
}
readUSB.js
this code should simply read a byte from USB and output it to console, what it does instead is to read nothing, and output null (usbcomm.read() returns null if it didn't read anything). This is just a proof of concept to make sure I can read() asynchronously, but things are obviously not working correctly.
const SerialPort = require('serialport')
const usbcomm = new SerialPort("COM4")
usbcomm.setEncoding('utf-8')
usb_rx();
function usb_rx()
{
console.log("running in child process!")
console.log(usbcomm.read(1))
}
if I run this code as an event handler in response to a "read" message from the renderer process, everything runs normally and I read correct responses from usb. I've confirmed this is not a problem with my usb device, and that in simple use cases usbcomm.read() behaves as expected.
I'm wondering now if node-serialport for whatever reason won't work with code run inside of fork().

You can make blocking calls from a renderer. As long as you keep them below 20-30ms and you don't swamp the process with blocking calls you'll be fine. We've used node-serialport and node-ffi extensively in the renderer process making dozens of calls per second transferring megabytes of data while keeping the UI responsive with no jerkiness.
Maybe you can't keep the blocking calls short enough? How about doing it in the main process? This is also a bad idea. Blocking the main process blocks IPC between the renderers and GPU process so you'll likely hang the UI this way too.
If you really have to make long blocking synchronous calls, make them in a hidden renderer process. You can block this as much as you like because it's not displaying any UI. To make this easier, you can use electron-remote - renderer-taskpool feature to bump long running tasks onto another renderer process.

Related

Electron: Cannot read properties of undefined (reading 'receive') | ipc cannot receive args I sent through HTML iframe

I am trying to send lightmode and language data through ipcMain to ipcRenderer through my preload script:
Preload.js:
const { contextBridge, ipcRenderer } = require("electron");
const ipc = {
render: {
send: ["mainMenuUpdate"],
receive: ["windowStats"],
},
};
contextBridge.exposeInMainWorld("ipcRender", {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, args) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, args);
}
},
});
I send the data like this:
mainMenu.js:
window.ipcRender.send('mainMenuUpdate', {
// windowSkin and currentLanguage are both coming from the previous window update
// and are an object with hexvalues and string respectively. Doesn't matter in this
// case
currentSkin: windowSkin,
currentLanguage,
})
Note that this has been working for all other windows I have updated but not this one.
I recieve this no problem in main.js as follows:
ipcMain.on('mainMenuUpdate', (event, args) => {
console.log(args);
// outputs correcly
mainWindow.webContents.send("windowStats", args);
});
and then sends it away through windowStats to myPage.js:
let currentLanguage;
let windowSkin;
window.ipcRender.receive("windowStats", (event, windowArgs) => {
console.log(windowArgs);
// does not output anything because of double error
windowSkin = windowArgs.currentSkin;
currentLanguage = windowArgs.currentLanguage;
updateColors(windowArgs.currentSkin);
langUpdate(currentLanguage);
});
I get a log when sending it from mainMenu.js, I get the same log when received in main.js, just before I send it with mainWindow.webcontents.send(//...) but no log because of the error.
Uncaught TypeError: Cannot read properties of undefined (reading
'receive') at myPage.js:18:18 (anonymous # myPage.js:18)
I get this two times, exactly the same error if that would help.
I have also tried to load in the new html file both before and after I send it through IPC.
The myPage.html looks as follows:
<body>
<main class="main-container">
<section class="left-column">
<div class="my-library-container">
<div class="left-column-btns myLibrary-btn" data-translation-key data-string="myPage:myLibrary-btn">??MY
LIBRARY
</div>
</div>
<div class="collections-container">
<div class="left-column-btns collections-btn" data-translation-key data-string="myPage:collections-btn">
??COLLECTIONS</div>
</div>
</section>
<section class="main-column">
<iframe class="main-screen" src="../myPage/myLibrary.html" frameborder="0"></iframe>
</section>
<section class="right-column"></section>
</main>
<script defer src="../../assets/js/myPage/myPage.js"></script>
<script defer src="../../constants/colors.js"></script>
<script defer src="../../constants/languages.js"></script>
</body>
which I show inside an iframe like this:
<body>
<main class="main-container">
<iframe class="main-window" src="myPage/myPage.html" frameborder="0"></iframe>
</main>
<script defer src="../assets/js/mainMenu.js"></script>
<script defer src="../constants/colors.js"></script>
<script defer src="../constants/languages.js"></script>
</body>
What may or may not contribute to the problem is that it seems like the javascript is loaded before the HTML even when JS file is deferred.
myPage.js ex.
const myLibraryBtn = document.querySelector('.myLibrary-btn')
myLibraryBtn.classList.add('activated')
and get this error:
Uncaught TypeError: Cannot read properies of null (reading
'classList') at ...
I will happily elaborate and extend this question because I have no idea what I even should look for and where.
UPDATE:
As I've been doing excessive testing I have concluded that I can't get anything through the ipc (main --> renderer) while the renderer is an iframe. I can put in a button to update the window normally without iframe, even though I still get a
Uncaught TypeError: Cannot read properties of undefined (reading
'receive') at myPage.js....
error. But it doesn't seem to interfere with my updateColors() and langUpdate() functions which show the HTML correctly. So the main problem is that I am not able to send anything through mainMenuUpdate channel and get anything but two errors as mentioned above while showing myPage.html inside an iframe. So I still get the error on both occations, while two times when console.log() does not work and one time when using a button instead of an iframe which gives one error of such and the console.log() with args for both updateColor() and langUpdate() functions.
As #midnight-coding mentioned about ipcRender.postMessage(channel, message, [transfer]); as an alternative to IPC which cannot be used to send data parent --> iframe; I decided to go with what I found from Mozilla webDocs and this 3 min YT video.
mainMenu.js: (renderer)
// iframe \/
const mainWindow = document.querySelector('.main-window');
mainWindow.contentWindow.postMessage({
channel: 'mainMenuUpdate',
currentSkin: windowSkin,
currentLanguage,
}, '*')
Because I send it directly through renderers I avoid extra code inside main.js, so it sends through to myPage.js (renderer) and receives it:
window.addEventListener('message', (message) => {
if(message.data.channel == 'mainMenuUpdate') {
// I now have args over to `iframe`
console.log(message.data)
//... updateColors(...) langUpdater(...)
}
});
I still don't know about myLibrary button, but it is not caused by the iframe problem.

Why are my Filepond uploads not working on Android devices only?

I am currently using Filepond to upload images to an S3 bucket. This functionality works great on a computer or on iOS devices but refuses to work on Android devices and I'm having the time of my life figuring out the root cause. It should upload just fine across all devices. Has anyone experienced any issues similar to this on an Android device using Filepond? I've also attached below the relevant Filepond code. I don't see any issues with it, but of course, I may be missing something. If anyone could offer a pointer or any kind of advice it would be greatly appreciated or if I left anything out please let me know. Thanks!
JS
<!-- Load FilePond library -->
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
<script src="//cdn.jsdelivr.net/npm/sweetalert2#10"></script>
<!-- include FilePond jQuery adapter -->
<script src="https://unpkg.com/jquery-filepond/filepond.jquery.js"></script>
<script type="application/javascript">
let tags = [];
let folderOpen = false;
let currentTag = "Uncategorized";
let imageUploadUrl = "{{ url_for('testing_jobs.job_image_upload', id=job.id) }}";
// Page init code
$( document ).ready(() => {
// Select Fields
$(".select2").select2();
// Setup FilePond inputs
$.fn.filepond.registerPlugin(FilePondPluginImagePreview);
// Turn input element into a pond
$('.filepond').filepond();
// Set FilePond properties
$('.filepond').filepond('allowMultiple', true);
$('.filepond').filepond('instantUpload', true);
$('.filepond').filepond('server', {
url: imageUploadUrl,
process: {
onload: (res) => {
// Select the response field to use as a unique ID
let image = JSON.parse(res).image;
if (folderOpen) {
getJobImages(currentTag);
} else {
alert("Image added")
}
return image.id;
}
}
});
// Listen for addfile event
$('.filepond').on('FilePond:addfile', function(e) {
console.log('file added event', e);
// Set the current tag for the image
e.detail.file.setMetadata("tag",currentTag);
});
// Listen for processfile event
$('.filepond').on('FilePond:processfile', function(e) {
console.log('file process event', e);
setTimeout(() => {
e.detail.pond.removeFile();
}, 2000);
});
Html for submitting
<div class="card">
<div class="card-header">
<h4>Upload Images</h4>
</div>
<div class="card-body">
<input type="file" class="filepond">
</div>
</div>
The issue turned out to be the image attachments on my Android device. Set up some checks for checking the file types and now it all works.

How update the vue template at the run-time?

I am working on the CMS based vue page. In the page I have one root container inside that I have two child container as looks below
<div id="app">
<div class="above-the-fold">...</div>
<div class="below-the-fold noscript-container">
<noscript>
<div v-if="true">{{someDynamicBinding}}</div>
<div v-if="false">{{someDynamicBinding}}</div>
</noscript>
</div>
</div>
If javascript is enabled I am removing noscript tag and appending inside contents to the parent element using a below script.
document.addEventListener("DOMContentLoaded", function () {
const appendContents = function () {
const noScript = document.querySelector(".noscriptcontainer noscript");
const belowContent = noScript.innerHTML;
noScript.parentElement.innerHTML += belowContent;
console.log("elm appended");
window.removeEventListener("scroll", appendContents);
noScript.remove();
console.log("eve removed");
};
window.addEventListener("scroll", appendContents);
});
Now the problem is vue doesn't evaluating v-if or {{dynamicBinding}}
Jsfiddle Link https://jsfiddle.net/69yr3mp5/2/
Now what is the best way to make this work?
Try to replace the second if with v-else-if
<div v-if="true"><h1>
{{someDynamicBinding}}
</h1></div>
<div v-else-if="false"><h1>
{{someDynamicBinding}}
</h1></div>
</noscript>
TL;DR;
You can update the template run-time, but you can't update the template after component is mounted.
Run-time, in the context of these frameworks, means that it is not pre-compiled, which is something that Vue supports. However, the problem here (one of them anyway) is that you are modifying the template after the application is mounted. The Vue app will not continue to check the template after the initial read. While I'm not sure what you're trying to accomplish here, It seems that the approach is wrong.
To illustrate, if you use this code, it will compile the updated code, but not until you scroll.
let mounted = false
document.addEventListener("DOMContentLoaded", function() {
const appendContents = function() {
const noScript = document.querySelector(".noscript-container noscript");
const belowContent = noScript.innerHTML;
noScript.parentElement.innerHTML += belowContent;
console.log("elm appended");
window.removeEventListener("scroll", appendContents);
noScript.remove();
console.log("eve removed");
if (!mounted) {
Vue.createApp({
data() {
return {
someDynamicBinding: 'Some Content'
};
},
mounted() {
console.log('mounted')
},
}).mount('#app');
};
mounted = true
}
window.addEventListener("scroll", appendContents);
});

Playing local mp3 files with react js without importing

I want to create an app that shows different songs i've chosen, with react.js
The problem is that it doesn't work with local files. Path is ok (just checked in the DOM)
I've tried with relative path and absolute path but it is still the same.
I've tried a lot of things, such as importing react-sound, react-audio-player...
I've tried to directly import the mp3 file at the beginning of the file with "import song from "./songs/song.mp3" and it works, but it is useless as you have to add it manually if you want to add/delete songs.
When I press the play button, it always fails the promise and returns me the error :
DOMException: Failed to load because no supported source was found.
import React from "react";
const SongCard = (props) => {
const playAudio = () => {
const audio = new Audio(props.song.path)
const audioPromise = audio.play()
if (audioPromise !== undefined) {
audioPromise
.then(() => {
// autoplay started
console.log("works")
})
.catch((err) => {
// catch dom exception
console.info(err)
})
}
}
return (
<div className="songCard">
<div className="coverContainer">
<img src=""/>
</div>
<div className="infoContainer">
<div className="playPauseButton" onClick={playAudio}>►</div>
<div className="songTitle">{props.song.nom}</div>
</div>
</div>
);
};
export default SongCard;
Does anyone have an idea ?
Due to security issues, you won't be able to access local files programatically from JavaScript running in browser.
The only way you can get a hold of local files is by:
User selecting the file via a file <input>
User drag and dropping the files into your application
This way the user is explicitly giving you access to those files.
You can either design your application around those interactions, or
You can start a web server locally where it has access to your audio files and stream those to your app or upload the files to a cloud service.
you can do it by using this code:
const SongCard = (props) => {
const playAudio = () => {
let path = require("./" + props.song.path).default;
const audio = new Audio(path);
const audioPromise = audio.play();
if (audioPromise !== undefined) {
audioPromise
.then(() => {
// autoplay started
console.log("works");
})
.catch((err) => {
// catch dom exception
console.info(err);
});
}
};
return (
<div className="songCard">
<div className="coverContainer">
<img src="" />
</div>
<div className="infoContainer">
<div className="playPauseButton" onClick={playAudio}>
►
</div>
<div className="songTitle">{props.song.nom}</div>
</div>
</div>
);
};
export default SongCard;
it'll work if you change the "./" in the require to the relative path of the audio's dictionary, and send only the name of the audio file in the parent's props
I hope I was help you

onClick handler not registering with ReactDOMServer.renderToString

I am trying to copy this fiddle:
http://jsfiddle.net/jhudson8/135oo6f8/
(I also tried this example
http://codepen.io/adamaoc/pen/wBGGQv
and the same onClick handler problem exists)
and make the fiddle work for server side rendering, using ReactDOMServer.renderToString
I have this call:
res.send(ReactDOMServer.renderToString((
<html>
<head>
<link href={'/styles/style-accordion.css'} rel={'stylesheet'} type={'text/css'}></link>
</head>
<body>
<Accordion selected='2'>
<AccordionSection title='Section 1' id='1'>
Section 1 content
</AccordionSection>
<AccordionSection title='Section 2' id='2'>
Section 2 content
</AccordionSection>
<AccordionSection title='Section 3' id='3'>
Section 3 content
</AccordionSection>
</Accordion>
</body>
</html>
)));
the Accordion element looks like so:
const React = require('react');
const fs = require('fs');
const path = require('path');
const Accordion = React.createClass({
getInitialState: function () {
// we should also listen for property changes and reset the state
// but we aren't for this demo
return {
// initialize state with the selected section if provided
selected: this.props.selected
};
},
render: function () {
// enhance the section contents so we can track clicks and show sections
const children = React.Children.map(this.props.children, this.enhanceSection);
return (
<div className='accordion'>
{children}
</div>
);
},
// return a cloned Section object with click tracking and 'active' awareness
enhanceSection: function (child) {
const selectedId = this.state.selected;
const id = child.props.id;
return React.cloneElement(child, {
key: id,
// private attributes/methods that the Section component works with
_selected: id === selectedId,
_onSelect: this.onSelect
});
},
// when this section is selected, inform the parent Accordion component
onSelect: function (id) {
this.setState({selected: id});
}
});
module.exports = Accordion;
and the AccordionSection component looks like so:
const React = require('react');
const AccordionSection = React.createClass({
render: function () {
const className = 'accordion-section' + (this.props._selected ? ' selected' : '');
return (
<div className={className}>
<h3 onClick={this.onSelect}>
{this.props.title}
</h3>
<div className='body'>
{this.props.children}
</div>
</div>
);
},
onSelect: function (e) {
console.log('event:',e);
// tell the parent Accordion component that this section was selected
this.props._onSelect(this.props.id);
}
});
module.exports = AccordionSection;
everything works, and the CSS is working, but the problem is that the onClick doesn't get registered. So clicking on the accordion elements does nothing. Does anyone know why the onClick handler might not get registered in this situation?
React DOM render to string only sends the initial HTML as a string without any JS.
You need a client side react router as well which will attach the required JS handlers to the HTML based on their react-id's. The JS needs to run on both sides.
Universal rendering boilerplate for quick start. https://github.com/erikras/react-redux-universal-hot-example
Another question which is similar to yours. React.js Serverside rendering and Event Handlers
None of the hooks will register with ReactDOMServer.RenderToString. If you want to accomplish server side rendering + hooks on your react component, you could bundle it on the client (webpack, gulp, whatever), and then also use ReactDOMServer.RenderToString on the server.
Here's a blog post that helped me accomplish this:
https://www.terlici.com/2015/03/18/fast-react-loading-server-rendering.html

Categories