Vuex & Websockets - node.js

So currently I am working with VueJS 2 and I am very new with it. Now I was getting some help with some other people, but I am still stuck.
Here is what I want to achieve (example - closely linked to what I want):
I have a NodeJS application that listens on WebSockets. The application listens for connections via WebSocket and will take JSON data, with a command and then a data object with any content needed for that command.
The command for example could be login, and the data be username and password. The login function on the NodeJS application will then take this data, do what it needs and then return it back over the socket, whether it was successful or not, and maybe include an ID and some user information for Vuex to pickup and place in it's state, for the front-end of the application to pick up/use.
Currently I am using this boiler plate: https://github.com/SimulatedGREG/electron-vue
Which has served me very well as a learning curve, due to me wanting to use Vue and Vuex to manage my application and then use WebSockets for managing data to and from the data server.
So if you look at the link I sent in app/src/renderer/ (this is where the main code is for vue and vuex).
A friend of mine added the following code for me as an example and I am stuck trying to get it into vuex as actions and mutations. He made it all in one vue component, so I am struggling on how it works with vuex. As I want to be able to access the (example) loginUser action from anywhere in the application (uses routes for multiple pages/views).
So in the MyApp/app/src/renderer/components/LandingPageView.vue
<template>
<div>
<img src="./LandingPageView/assets/logo.png" alt="electron-vue">
<h1>Welcome.</h1>
<current-page></current-page>
<websocket-output></websocket-output>
<versions></versions>
<links></links>
</div>
</template>
<script>
import CurrentPage from './LandingPageView/CurrentPage'
import Links from './LandingPageView/Links'
import Versions from './LandingPageView/Versions'
import WebsocketOutput from './LandingPageView/WebsocketOutput'
export default {
components: {
CurrentPage,
Links,
Versions,
WebsocketOutput
},
name: 'landing-page'
}
</script>
<style scoped>
img {
margin-top: -25px;
width: 450px;
}
</style>
That is the updated file for that, and then below is the code for the MyApp/app/src/renderer/components/LandingPageView/WebsocketOutput.vue
<template>
<div>
<h2>Socket output:</h2>
<p v-text="socket_out"></p>
</div>
</template>
<script>
var ws = require("nodejs-websocket")
export default {
data () {
return {
socket_out: "connecting to the websocket server..."
}
},
mounted () {
const parent = this
var connection = ws.connect("ws://dannysmc.com:9999", {}, function (conn) {})
connection.on("text", function (text) {
console.log('Text received: ' + text)
parent.socket_out = text
})
connection.on("connect", function () {
connection.send('yo')
})
},
created () {
// Set $route values that are not preset during unit testing
if (process.env.NODE_ENV === 'testing') {
this.$route = {
name: 'websocket-output',
path: '/websocket-output'
}
}
}
}
</script>
<style scoped>
code {
background-color: rgba(46, 56, 76, .5);
border-radius: 3px;
color: #fff;
font-weight: bold;
padding: 3px 6px;
margin: 0 3px;
vertical-align: bottom;
}
p {
line-height: 24px;
color: red;
}
</style>
Everything else is just the boiler plate that you see, so if anyone is willing to help me and give me some tips of what to read that explains this or anything else? as I can't find much information on it unfortunately.

I have an electron application that uses Vue and a websocket for information and here is how I set mine up.
I have a store defined that also actually creates and sets up the websocket.
Store.js
const socket = require("socket-library") // Take your pick of socket libs
const mySocket = new socket(...)
mySocket.on("message", message => store.handleMessage(message))
...other handlers...
const store = {
handleMessage(message){
// do things with the message
}
}
export default store
Renderer.js
import store from "./store"
new Vue({
data:{
store
}
})
This exposes my store at the root level of my Vue and allows me to pass data to components, or what have you. The store manages all the incoming information from the websocket.
With you wanting to use Vuex, you could do essentially the same thing, where Vuex would be your store and when messages come in over the socket, you just pass them to Vuex.
mySocket.on("message", msg => vuexStore.dispatch("onSocketMessage", msg))
and set up your Vue and components to work with Vuex as you typically would.

Related

Empty Div Preventing Interaction with amCharts5 MapChart on Vue3

I decided to dip my toes in Vue and have had an idea for a website for a while which I'd like to use amCharts5 for.
I had some issues initially as all the info I could find was related to Vue2, but I think I've somewhat wrapped my head around Vue3 and its composition API.
The MapChart is created, however there is always a div slapped on top of it which prevent any interaction. If I delete this element via DevTools, the MapChart becomes interactive.
I've tried debugging this and commenting sections of the code out, regardless this div is always created. And I simply can't figure out if it's injected by Vue or if amCharts 5 is the culprit.
The highlighted element is the one I must delete for it to become interactive.
Here's how the component is setup;
<template>
<div class="testClass" ref="chartdiv">
</div>
</template>
<script setup lang="ts">
import * as am5 from "#amcharts/amcharts5";
import * as am5map from "#amcharts/amcharts5/map";
import am5geodata_worldLow from "#amcharts/amcharts5-geodata/worldLow";
import am5themes_Animated from '#amcharts/amcharts5/themes/Animated';
import { ref, onMounted, onUnmounted } from "vue";
const chartdiv = ref<HTMLElement | null>()
var root!: am5.Root;
onMounted(() => {
if (chartdiv.value) {
// Create the Root
var root = am5.Root.new(chartdiv.value);
// Setup the MapChart
var chart = root.container.children.push(
am5map.MapChart.new(root, {
panX: "rotateX",
panY: "rotateY",
projection: am5map.geoOrthographic(),
centerMapOnZoomOut: false
})
);
// Setup Animations
root.setThemes([
am5themes_Animated.new(root)
]);
// Create MapPolygons
var polygonSeries = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldLow
})
);
// Setup MapPolygon Styling
polygonSeries.mapPolygons.template.setAll({
tooltipText: "{name}",
fill: am5.color("#909090")
});
// Setup MapPolygon Hover Styling
polygonSeries.mapPolygons.template.states.create("hover", {
fill: am5.color("#FF0000"),
stroke: am5.color("#00FF00"),
strokeWidth: 2
});
polygonSeries.mapPolygons.template.events.on("click", function(event) {
//console.log("Clicked: {0}", event.target);
});
// Setup Background
var backgroundSeries = chart.series.unshift(
am5map.MapPolygonSeries.new(root, {})
);
backgroundSeries.mapPolygons.template.setAll({
fill: am5.color(0x2c84d0),
stroke: am5.color(0x2c84d0)
});
backgroundSeries.data.push({
geometry: am5map.getGeoRectangle(90, 180, -90, -180)
});
}
});
onUnmounted(() => {
if (root) {
root.dispose();
}
});
</script>
<style scoped>
.testClass {
width: 50vw;
height: 50vh;
}
</style>
When you create a Vite-powered Vue project, it automatically creates a bunch of CSS files for you. One of those is base.css.
Inside this file, you'll find these lines which causes all the headache;
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
position: relative;
font-weight: normal;
}
Removing those lines will fix the issue.

Vue.js Application - video element denies streaming on mobile

I'm working on a Vue.js web app that needs to support video streaming. The backend is a Node.js app. It's pulling the videos from an S3 bucket and sending an unbuffered stream to the client. Here's the frontend code:
<template>
<div class="page-container">
<div v-if="currentVideo" class="pageContent">
<section-head>{{ currentVideo.name }}</section-head>
<p>{{ currentVideo.description }}</p>
<video
v-if="videoUrl"
:poster="currentVideo.thumbnail"
playsinline
controls
controlslist="nodownload"
class="stream"
type="video/mp4"
:key="videoUrl"
:src="videoUrl"
/>
<section-head>See More</section-head>
<gallery />
</div>
<h1 v-else class="sorry">
If you're seeing this message, you may have accidently gone to the wrong page.
Please go to the <router-link to="/">Home</router-link> page.
</h1>
</div>
</template>
<script>
import sectionHead from '../components/atoms/Header/SectionHead.vue';
import config from '../../config.js';
import gallery from '../components/molecules/Gallery/Gallery.vue';
export default {
name: 'Stream',
components: { sectionHead, gallery },
computed: {
currentVideo() {
return this.$store.state.currentVideo;
},
videoUrl() {
return 'https://' + config.currentEnvAPI() + '/stream/' + this.currentVideo.video;
},
},
};
</script>
<style lang="scss" scoped>
#import '../styles/_variables.scss';
.page-container {
margin: 0 auto;
p {
text-align: center;
font-size: 25px;
margin: 0 0 6px;
}
}
.stream {
display: block;
margin: 0 auto;
width: 750px;
height: auto;
outline: none;
#include tablet {
width: 650px;
}
#include phone {
width: 100%;
}
}
.sorry {
text-align: center;
padding: 120px;
}
</style>
And I'm setting and getting the video object from VueX here:
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions.js';
import mutations from './mutations.js';
Vue.use(Vuex);
const state = {
videoList: [],
videoObjects: [],
loadingData: false,
currentVideo: null,
currentGallery: [],
};
export default new Vuex.Store({
state,
actions,
mutations,
});
This is the route that I'm calling on the backend:
app.get('/stream/:video', async (req, res) => {
let videoParams = {
Bucket: BUCKET_NAME,
Key: req.params.video,
};
S3.getObject(videoParams)
.on('httpHeaders', function (statusCode, headers) {
res.set('Content-Length', headers['content-length']);
res.set('Content-Type', headers['content-type']);
res.set('Accept-Ranges', headers['accept-ranges']);
this.response.httpResponse.createUnbufferedStream()
.pipe(res);
})
.send();
});
As far as I can tell, this is a good implementation because it works fine on desktop, both locally and deployed. I only have issues on mobile browsers. I've tried both Chrome and Safari on two different iPhones (no access to an Android device). This is all I see for all of my videos:
I also took the time to set up an SSL certificate for all of my endpoints, so I know the videos are streaming over https. I was thinking that the size of the videos (between 250 - 550 MB) may be the issue, but I also noticed that even though the element is disabled, the connection still transfers the whole thing.
I would think if it was a server side issue that it wouldn't send the whole file, but it does. So wouldn't the issue be client side? I can't figure out why it's not working though. The files are mp4 and I'm sure that they should work because I've tried other links to test with mp4 videos and they've worked. I've changed up the element attributes a lot too. I've tried both with and without playsinline, autoplay, and muted. I've tried having the source element as a child of the video element and that still didn't work. I'm also not getting any console errors, so I can't figure out what the actual problem is and I'm not sure how else I can troubleshoot this.
the h.264 profile of your example file in the comments is "high" but ios safari only supports "base". if you open the file directly in safari, it won't play either.
see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW9

How to change static css file on API response in ReactJS

In my project i want to change the background-color and font of text. Both the properties are written in css file.
Project structure is:
|-myProject
|--public
|--src
|--package.json
All my css is written in public directory, and i have an api which give response of background-color and font. Now i want to change the properties background-color and font in css files according to api response.
Instead of trying to modify the base stylesheets, why not set these particular properties using the elements’ style attributes:
const divStyle = {
backgroundColor: /* Some color */,
fontFamily: /* Some font stack */,
};
function HelloWorldComponent() {
return <div style={ divStyle }>Hello World!</div>;
}
(adapted from the React docs)
I think the best way to do this would be to use inline style on the elements you want to change.
On api response -> set
const yourVar={
backgroundColor:##,
fontFamily:##
};
I believe that the answer from MTCoster is the best approach. depending on the structure of your app you could use the new Context API to make some sort of theme provider, so that you could pass custom styles that could be stored on your application state and that is loaded from your backend API. there are some tools that could help you integrate this feature more easily, like Styled-Components.
with Styled components you culd write something like:
import styled from 'styled-components'
import { YourComponentJSX } from '../somewhere'
// Wrap the component where you need your custom styles
const YourStyledComponent = styled(YourComponentJSX)`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
/* Color the border and text with theme.main */
// using the short-if to add a default color in case it is not connected to the ThemeProvider
color: ${props => props.theme.main ? props.theme.main : "palevioletred"};
border: 2px solid ${props => props.theme.main ? props.theme.main : "palevioletred"};
`;
// Define what props.theme will look like
const theme = {
main: "mediumseagreen"
};
render(
<div>
<ThemeProvider theme={theme}>
<App>
<YourStyledComponent>Themed</YourStyledComponent>
</App>
</ThemeProvider>
</div>
);
This way you could wrap your whole app and use custom styles saved on the app state that have been loaded from the backend and use them on really deeply nested ui components
*The code is a modification from the styled-component docs

Trouble verify google invisible recaptcha with node.js

I am using the npm recaptcha verify plugin:
https://www.npmjs.com/package/recaptcha-verify
In my react app I am using
https://www.npmjs.com/package/react-google-invisible-recaptcha
At the top of my node app code:
var Recaptcha = require('recaptcha-verify');
var recaptcha = new Recaptcha({
secret: 'secret_key',
verbose: true
});
And then the route that works fine to send the email without recaptcha...
router.post('/mailer/recaptcha', function(req, res) {
var userResponse = req.query['g-recaptcha-response'];
console.log("user response: ", userResponse)
recaptcha.checkResponse(userResponse, function(error, response){
if(error){
// an internal error?
res.status(400).render('400', {
message: error.toString()
});
return;
}
if(response.success){
res.status(200).send('the user is a HUMAN :)');
// save session.. create user.. save form data.. render page, return json.. etc.
}else{
res.status(200).send('the user is a ROBOT :(');
// show warning, render page, return a json, etc.
}
});
In the form, using the react plugin, I am trying to follow the documentation as well, and it currently looks like this.
<Recaptcha
ref={ ref => this.recaptcha = ref }
sitekey="site_key"
onResolved={ this.testRecaptcha } />
The onResolved function attempts to validate the Recaptcha. this.testRecaptcha is a function that dispatches to our node route as seen above. In that route, where I console.log the userResponse,, I am getting undefined. That appears to be the main issue here, I think. The req also logs out all of the items in my form as part of the req.body, but nothing indicates that the recaptcha field is actually there.
testRecaptcha(e) {
let myObject = Object.assign({}, this.state.form, { sentFrom: 'contact', sendTo: this.state.sendTo });
this.props.dispatch(actions.sendTestToMailer(myObject));
}
When I inspect the code that is output from the recaptcha component it looks like this:
<div style="display: none;"><div class="grecaptcha-badge" style="width: 256px; height: 60px; transition: right 0.3s ease; position: fixed; bottom: 14px; right: -186px; box-shadow: gray 0px 0px 5px;"><div class="grecaptcha-logo"><iframe src="https://www.google.com/recaptcha/api2/anchor?k=sitekey&co=aHR0cDovL2xvY2FsaG9zdDo4MDgx&hl=en&v=v1514934548259&size=invisible&cb=oh5n23icp55m" width="256" height="60" role="presentation" frameborder="0" scrolling="no" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation allow-modals allow-popups-to-escape-sandbox"></iframe></div><div class="grecaptcha-error"></div><textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 40px; border: 1px solid #c1c1c1; margin: 10px 25px; padding: 0px; resize: none; display: none; "></textarea></div></div>
(where sitekey is the actual key -- not the text 'sitekey)
but, i receive the following error from the node.js app
{ success: false, 'error-codes': [ 'invalid-input-response' ] }
It seems I am not pushing the recaptcha data into this.state.form, but I am not sure what object needs to be pushed into that or if that is even the issue.
Does anyone have any insight on this? Is there an easier way to verify the invisible recaptcha? There is little to no documentation or working examples with every step to take here with node and react. Hopefully someone can help me and anyone else in a similar situtation?
------- EDIT -------------------------------------------------------
Based on feedback from trixn, made these changes and its almost working...
testRecaptcha(e) {
let recaptchaResponse = this.recaptcha.getResponse();
let myObject = Object.assign({}, this.state.form, { sentFrom: 'contact', sendTo: this.state.sendTo, recaptchaResponse:recaptchaResponse });
this.props.dispatch(actions.sendTestToMailer(myObject));
}
AND...
in the node backend:
var userResponse = req.body.recaptchaResponse;
recaptcha.checkResponse(userResponse, function(error, response){ etc..});
but... I am now getting this error.
Error parsing the response to an object. AND 'No default engine was specified and no extension was provided.'
You need to get the response token of your solved reCaptcha by accessing this.recaptcha.getResponse() in your onResolved callback then add that to your POST data and validate that in your node backend.

Isolated styled-components with #font-face

I'm using https://github.com/styled-components/styled-components.
I'm trying to work out the best strategy for components that require #font-face. I want to make sure each component is independent of its context, so I'm defining font-family styles on each them. But if I use injectGlobal in multiple components, I get multiple #font-face rules for the same font.
Should I just define the #font-face rules in my ThemeProvider entry-point component and live with the fact that the desired font might not be loaded by the browser?
That's exactly why we made injectGlobal. If you take a look at our docs they say:
We do not encourage the use of this. Use once per app at most, contained in a single file. This is an escape hatch. Only use it for the rare #font-face definition or body styling.
So what I'd do is create a JS file called global-styles.js which contains this code:
// global-styles.js
import { injectGlobal } from 'styled-components';
injectGlobal`
#font-face {
font-family: 'My custom family';
src: url('my-source.ttf');
}
`
As you can see, all we do here is inject some global styling-in this case #font-face. The last thing necessary to make this work is to import this file in your main entry point:
// index.js
import './global-styles.js'
// More stuff here like ReactDOM.render(...)
This will mean your font-face is only injected once, but still all of your components have access to it with font-family: 'My custom family'!
Doing it this way will give you a flash-of-invisible-text (FOIT), but that has nothing to do with styled-components-it'd be the same if you were using vanilla CSS. To get rid of the FOIT requires a more advanced font loading strategy rather than just #font-faceing.
Since this has nothing to do with styled-components, I highly recommend watching these two Front End Center episodes to learn more about how to best do this: "Crafting Web Font Fallbacks" and "The Fastest Webfont Loader in the West"!
And on the other side of the same coin in Chrome:
do not use #font-face inside injectGlobal if using e.g. react-router.
You will get re-paint of all of you app on each newly loaded route.
And this is why:
Same font-files downloaded on each new route.
As soon as you include font-face in a separate .css file - problem solves as stated in the last comment in this github issue.
injectGlobal is deprecated. Use createGlobalStyle
import { createGlobalStyle } from 'styled-components'
export const GlobalStyle = createGlobalStyle`
body {
font-family: 'Muli', sans-serif;
h1 {
font-weight: 800;
font-size: 36px;
p {
font-size: 18px;
}
}
`;
Then you can use it in your App.js:
import { GlobalStyle } from './styles/global'
class App extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<>
<GlobalStyle/>
<Container/>
</>
</ThemeProvider>
)
}
}
I agree
I get reloaded with
import { createGlobalStyle } from 'styled-components';
import { silver } from 'shared-components/themes/colors';
export default createGlobalStyle`
#font-face {
font-family: "Proxima Nova";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("/static/media/fonts/proxima_nova/ProximaNova_300.otf");
}
and react create app

Resources