I'm working on a simple speech to text web app, I already have working server-side nodejs code and simple react page, but I don't have any idea how to glue them together, I was trying to implement various different weird stuff and either the react app doesn't get any data in return either there is error 500.
I would like to implement stop() function for the recorder after getting the transcription from Google as it is set to be listening only short commands, but the only solution that I could implement was the setTimeout function which is not exactly what I wanted.
EDIT: I have solved the problem with stopping recorder after getting the command and it works pretty good, however, any improvements are welcome. The solution was quite easy, I just modified the threshold from null to 0.5, thresholdEnd: 0.5. Still not resolved the front end for this express app.
EDIT 2: Funny thing, I accidentally discover this stuff and it is exactly what I wanted... So much effort just to find this amazing and super easy solution, especially if you follow this medium article.
Can anybody help me, please?
Server-side code:
'use strict';
const express = require('express');
const app = express();
const port = 3002;
const cors = require('cors')
// Node-Record-lpcm16
const recorder = require('node-record-lpcm16');
// Imports the Google Cloud client library
const speech = require('#google-cloud/speech');
function speechFunction() {
const encoding = 'LINEAR16';
const sampleRateHertz = 16000;
const languageCode = 'en-US';
const command_and_search = 'command_and_search';
const keywords = ['turn on', 'turn off', 'turn it on', 'turn it off'];
const request = {
config: {
encoding: encoding,
sampleRateHertz: sampleRateHertz,
languageCode: languageCode,
model: command_and_search,
speech_contexts: keywords
},
singleUtterance: true,
interimResults: false // If you want interim results, set this to true
};
// Creates a client
const client = new speech.SpeechClient();
// Create a recognize stream
const recognizeStream = client
.streamingRecognize(request)
.on('error', console.error)
.on('data', data =>
// process.stdout.write(
console.log(
data.results[0] && data.results[0].alternatives[0]
? `Transcription: ${data.results[0].alternatives[0].transcript}\n`
: `\n\nReached transcription time limit, press Ctrl+C\n`
)
);
// Start recording and send the microphone input to the Speech API
recorder
.record({
sampleRateHertz: sampleRateHertz,
threshold: 0, //silence threshold
recordProgram: 'rec', // Try also "arecord" or "sox"
silence: '5.0', //seconds of silence before ending
endOnSilence: true,
thresholdEnd: 0.5
})
.stream()
.on('error', console.error)
.pipe(recognizeStream);
console.log('Listening, press Ctrl+C to stop.');
// [END micStreamRecognize]
}
app.use(cors());
app.use('/api/speech-to-text/',function(req, res){
speechFunction(function(err, result){
if (err) {
console.log('Error retrieving transcription: ', err);
res.status(500).send('Error 500');
return;
}
res.send(result);
})
});
// app.use('/api/speech-to-text/', function(req, res) {
// res.speechFunction();
// });
// app.get('/speech', (req, res) => res.speechFunction);
app.listen(port, () => {
console.log(`Listening on port: ${port}, at http://localhost:${port}/`);
});
React
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super()
this.state = {
}
}
onListenClick() {
fetch('http://localhost:3002/api/speech-to-text/')
.then(function(response) {
console.log(response);
this.setState({text: response});
})
.catch(function(error) {
console.log(error);
});
}
render() {
return (
<div className="App">
<button onClick={this.onListenClick.bind(this)}>Start</button>
<div style={{fontSize: '40px'}}>{this.state.text}</div>
</div>
);
}
}
export default App;
Replace onListenClick() code with the following
async onListenClick(){
const response= await axios.get('http://localhost:3002/api/speech-to-text/')
console.log(response)
this.setState(text:response.data)
}
I am trying to figure out how to stop the google api by clicking on a button. But above code will bring data on the react
Related
I've got a simple React App with Nodejs/Express server.
Right now I'm trying to obtain an ID value from Serialport and compare that ID with the ID from an external webservice and retrieve the data I get from the webservice and display it on the frontend.
I've got the first part working and right now I can compare both ID's and fetch the data but can't figure out how to display it in the React frontend.
This is my server index.js:
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname + "/public")));
const PORT = process.env.PORT || 5000;
const scanProducts = () => {
const scan = require('./actions/scanProduct');
scan.scanner();
//can't reach data here
}
app.listen(PORT);
scanProducts();
This is my scanProduct.js
const { SerialPort } = require('serialport');
module.exports = {
scanner: function(){
const port = new SerialPort({ path: 'COM6', baudRate: 9600, autoOpen: false });
port.open(function (err) {
if (err) {
return console.log('Error opening port: ', err.message)
}
port.write('main screen turn on')
})
port.on('open', function() {
setInterval(function(){
const portReader = port.read();
if(portReader != null){
const sensorVal = Buffer.from(portReader).toString();
const soap = require('soap');
const url = 'http://example.com?wsdl';
soap.createClient(url, function(err, client) {
client.GetProductById({ UserId: "1", ProductId: sensorVal }, function(err, result) {
if(err) return console.log(err);
console.log(result.GetProductByIdResult);
return result.GetProductByIdResult; //returns nothing into index.js
});
});
}
}, 500);
})
}
}
Then I would like to add each product from the server into this custom component, right now it displays static data fetched from a basic json file.
This my component ProductsList.js:
import ProductRow from "./ProductRow";
import './ProductsList.css';
const ProductsList = props => {
return(
<>
<div id="products-wrapper" className="w-9/12 h-full px-20 py-20 flex flex-col overflow-x-hidden overflow-y-scroll ">
<div className="w-full h-auto my-2 px-3 py-3 font-semibold grid grid-cols-3 bg-blue-600 rounded-xl">
<div className="w-[60%] text-left">Produto</div>
<div className="w-[20%] text-left">Quant.</div>
<div className="w-[20%] text-left">Preço</div>
</div>
{props.items.map(product => <ProductRow name={product.name} quantity={product.quantity} price={product.price}/>)}
//here is where I need to add each product from the server
</div>
</>
);
}
export default ProductsList;
Any idea what I might be missing?
Any help would be greatly appreciated.
A sample code for your project
I put some comments, I hope it will be easy to follow
I wrote some remarks at the end of the code too
Backend
For the index.js (main) file of your backend
const EventEmitter = require('events');
var cors = require('cors')
// Init express app
const app = require('express')();
// Import your scanner utility
const scan=require('./scanProduct')
/*Enabling CORS for any origin (host and port) to test the service from a another origin (react client) .
It depends how you deploy your services */
app.use(cors({
origin:'*'
}))
// Init the http server
const server = require('http').createServer(app);
// Init the websocket library
const io = require('socket.io')(server);
// Define some plain emitter
const emitter=new EventEmitter()
// Pass the event emitter in it
scan.scanner(emitter)
// Event fired when the websocket has been established
io.on('connection', socket => {
// When the scanProduct fire an event on scanner channel
emitter.on("scanner",function(data){
// Emit the data on the websocket for the react client
// You can define whatever name you prefer, here also scanner
socket.emit('scanner',data)
})
});
//Listening on port 8080
server.listen(8080);
And for the scanProduct.js file i just "mocked" your implementation with setInterval every 2 secs in my case
module.exports = {
scanner: function(eventEmitter){
// Read serial port every 2000ms
setInterval(()=>{
/*
- Read on serial port
- fetch the data from the soap web service
*/
// just some data to send but should be from your workflow
const productResult=new Date().toISOString()
// you emit on the channel you defined in the index.js file
eventEmitter.emit("scanner",productResult)
},2000)
}
}
The depedencies I am using on the backend
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"socket.io": "^4.6.0"
}
Frontend
For the front-end, I used npx create-react-app to init the project,
And I edited only one file, the App.js file
import React, { useState, useEffect } from 'react';
import io from 'socket.io-client';
// Websocket initialization, use your config or .env file to set up the url
const websocket = io(
"http://localhost:8080",
{ transports: ['websocket'] }
)
function App() {
//State keeping all the products we are displaying
const [products, setProducts] = useState([])
//When App component is mounted
useEffect(() => {
// Just to log the connection
websocket.on('connection', () => {
console.log("Websocket connected")
})
// When websocket receive data on the scanner channel, emitted from the backend
websocket.on("scanner", (product) => {
// Update the products with new ones
setProducts(prevProducts => [...prevProducts, product])
})
}, [])
return (
<div>
<div>
Number of products : {products.length}
</div>
{
products.map((data) => {
return (
<div>
{data}
</div>
)
})
}
</div>
);
}
export default App;
My sample code as it is will send data every 2000ms and we will have en enormous list in no time ^^'.
For your use case, I do not know if it is relevant to send data every 500ms to the client.
Do you have continuous flow of valuable/usefull data on the serial port ? If you really have valuable data every 500ms, another approach would be more appropriate, like batching. The idea will be to save somewhere the products you collected every 500ms, and from titme to time, every 10 secondes for example, you send this list of new products to the client.
If the payload on the serial port arrives randomly, you need to build some logic into your setInterval function, and only trigger an event when necessary.
The sample of course lacks some features. You only have new products coming from the websocket, but no history. If you refresh your browser page or if the socket is closed for whatever reason, you are going to miss some products. If history is important, you need some persistence layer.
I'm building a web-based remote control application for the music program Ableton Live. The idea is to be able to use a tablet on the same local network as a custom controller.
Ableton Live runs Python scripts, and I use this library that exposes the Ableton Python API to Node. In Node, I'm building an HTTP/Websocket server to serve my React frontend and to handle communication between the Ableton Python API and the frontend running Redux/RTK Query.
Since I both want to send commands from the frontend to Ableton Live, and be able to change something in Ableton Live on my laptop and have the frontend reflect it, I need to keep a bi-directional Websocket communication going. The frontend recreates parts of the Ableton Live UI, so different components will care about/subscribe to different small parts of the whole Ableton Live "state", and will need to be able to update just those parts.
I tried to follow the official RTK Query documentation, but there are a few things I really don't know how to solve the best.
RTK Query code:
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react';
import { LiveProject } from '../models/liveModels';
export const remoteScriptsApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:9001' }),
endpoints: (builder) => ({
getLiveState: builder.query<LiveProject, void>({
query: () => '/completeLiveState',
async onCacheEntryAdded(arg, { updateCachedData, cacheDataLoaded, cacheEntryRemoved }) {
const ws = new WebSocket('ws://localhost:9001/ws');
try {
await cacheDataLoaded;
const listener = (event: MessageEvent) => {
const message = JSON.parse(event.data)
switch (message.type) {
case 'trackName':
updateCachedData(draft => {
const track = draft.tracks.find(t => t.trackIndex === message.id);
if (track) {
track.trackName = message.value;
// Components then use selectFromResult to only
// rerender on exactly their data being updated
}
})
break;
default:
break;
}
}
ws.addEventListener('message', listener);
} catch (error) { }
await cacheEntryRemoved;
ws.close();
}
}),
})
})
Server code:
import { Ableton } from 'ableton-js';
import { Track } from 'ableton-js/ns/track';
import path from 'path';
import { serveDir } from 'uwebsocket-serve';
import { App, WebSocket } from 'uWebSockets.js';
const ableton = new Ableton();
const decoder = new TextDecoder();
const initialTracks: Track[] = [];
async function buildTrackList(trackArray: Track[]) {
const tracks = await Promise.all(trackArray.map(async (track) => {
initialTracks.push(track);
// A lot more async Ableton data fetching will be going on here
return {
trackIndex: track.raw.id,
trackName: track.raw.name,
}
}));
return tracks;
}
const app = App()
.get('/completeLiveState', async (res, req) => {
res.onAborted(() => console.log('TODO: Handle onAborted error.'));
const trackArray = await ableton.song.get('tracks');
const tracks = await buildTrackList(trackArray);
const liveProject = {
tracks // Will send a lot more than tracks eventually
}
res.writeHeader('Content-Type', 'application/json').end(JSON.stringify(liveProject));
})
.ws('/ws', {
open: (ws) => {
initialTracks.forEach(track => {
track.addListener('name', (result) => {
ws.send(JSON.stringify({
type: 'trackName',
id: track.raw.id,
value: result
}));
})
});
},
message: async (ws, msg) => {
const payload = JSON.parse(decoder.decode(msg));
if (payload.type === 'trackName') {
// Update track name in Ableton Live and respond
}
}
})
.get('/*', serveDir(path.resolve(__dirname, '../myCoolProject/build')))
.listen(9001, (listenSocket) => {
if (listenSocket) {
console.log('Listening to port 9001');
}
});
I have a timing issue where the server's ".ws open" method runs before the buildTrackList function is done fetching all the tracks from Ableton Live. These "listeners" I'm adding in the ws-open-method are callbacks that you can attach to stuff in Ableton Live, and the one in this example will fire the callback whenever the name of a track changes. The first question is if it's best to try to solve this timing issue on the server side or the RTK Query side?
All examples I've seen on working with Websockets in RTK Query is about "streaming updates". But since the beginning I've thought about my scenario as needing bi-directional communication using the same Websocket connection the whole application through. Is this possible with RTK Query, and if so how do I implement it? Or should I use regular query endpoints for all commands from the frontend to the server?
I'm new in programming and trying to understand SSE. I'm using React for frontend , Node.js for backend and MongoDB for database. Sorry for my english in advance.
When i open the website, creating eventsource and start to listen backend "/test". I have a button in frontend and when i click it , a random float number between 0 and 1 posted backend "/savedata". Also showing numbers bigger than 0.5.
In server side for "/test", checking database for the new records every 3 seconds and if recorded numbers is bigger than 0.5, send it to frontend and delete the database record.
For "/savedata", saving numbers to database coming from frontend.
My Question is;
when i open website in a new tab, another eventsource is being created and trigger database "/test". Is there a way to reuse eventsource instead of create a new eventsource ? Also if you have any suggestions for my code, pls tell me. i'm trying to learn.
Here is my code;
Frontend React - FrontendTest.js
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const FrontendTest = () => {
const [data, setData] = useState(null)
const [databaseInfo, setDatabaseInfo] = useState(null)
let number = 0
const url = 'http://localhost:5000/test'
let source
useEffect(() => {
source = new EventSource(url)
source.onmessage = (e) => {
setData(JSON.parse(e.data))
}
}, [])
const buttonClicked = async (e) => {
e.preventDefault()
number = Math.random()
const sendReq = await
axios.post('http://localhost:5000/savedata', {
number,
})
setDatabaseInfo(sendReq.data)
}
return (
<div>
<div>
<button onClick={buttonClicked}>Send</button>
<p>{`If the number > 0.5 , it will be founded`}</p>
<p>
{databaseInfo &&
`${databaseInfo.data.toFixed(4)} Saved to
Database !`}
</p>
<p>
{data && `${data.toFixed(4)} Founded ! Database
input deleted ! `}
</p>
</div>
</div>
)
}
Node.js - server.js
import express from 'express'
import cors from 'cors'
import expressAsyncHandler from 'express-async-handler'
import mongoose from 'mongoose'
import Datas from './model.js'
const app = express()
const port = 5000
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true,
})
)
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
let interval
app.post(
'/savedata',
expressAsyncHandler(async (req, res) => {
const data = req.body.number
const result = await Datas.create({
data1: data,
})
res.send({ data })
})
)
app.get(
'/test',
expressAsyncHandler(async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
clearInterval(interval)
interval = setInterval(async () => {
const database = await Datas.find({})
const databaseData1 = database.map((item) => item.data1)
const databaseIds = database.map((item) => item._id)
const data = {
value: databaseData1,
}
for (let i = 0; i < data.value.length; i++) {
if (data.value[i] > 0.5) {
console.log(data.value[i])
res.write(`data: ${JSON.stringify(data.value[i])}\n\n`)
await Datas.findByIdAndDelete(databaseIds[i])
}
}
console.log('Searching')
}, 3000)
})
)
mongoose
.connect(CONNECTION_URL, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() =>
app.listen(port, () =>
console.log(`Example app listening at
http://localhost:${port}`)
)
)
.catch((error) => console.log(error))
You can't directly share the event source handle, but what you are able to do is share data between tabs, when they are in the same origin.
One approach would be LocalStorage. So the first tab would write to the storage that it is going to run the EventSource connection. The second tab that connects would see there is already another tab open, and start listening. Then each time an event comes into the first tab, it writes it to local storage, and the second tab can see it.
(You need to handle the case of what happens if the first tab closes; especially if there are multiple other tabs listening in, so be aware that making this production-ready is going to get quite complicated.)
Another WebAPI that is specifically for doing that kind of thing is Broadcast Channel. I've not used it: the browser support is almost the same as EventSource, but Safari is lagging behind.
It looks like this question was created to keep track of the various approaches.
I am new to React/redux with Node. I am working on a full stack app that utilizes Node.js on the server side and React/Redux on the client side. One of the functions of the app is to provide a current and eight-day weather forecast for the local area. The Weather route is selected from a menu selection on the client side that menu selection corresponds to a server side route that performs an axios.get that reaches out and consumes the weather api (in this case Darksky) and passes back that portion of the JSON api object pertaining to the current weather conditions and the eight-day weather forecast. There is more to the API JSON object but the app consume the "current" and "daily" segment of the total JSON object.
I have written a stand-alone version of the server-side axios "get" that successfully reaches out to the Darksky API and returns the data I am seeking. I am, therefore, reasonably confident my code will correctly bring back the data that I need. My problem consists in this: when I try to render the data in my React Component, the forecast object is undefined. That, of course, means there is nothing to render.
I have reviewed my code, read a plethora of documentation and even walked through tutorials that should help me find the problem and it still eludes me. So, I am stuck and would greatly appreciate some help. Most of the comment you still in the code below will be removed after the debugging process is completed.
I am including code blocks relevant to the problem:
My React Component
// client/src/components/pages/functional/Weather.js
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Moment from 'react-moment';
import Spinner from '../../helpers/Spinner'
import { getWeather } from '../../../redux/actions/weather'
const Weather = ({ getWeather, weather: { forecast, loading } }) => {
// upon load - execute useEffect() only once -- loads forecast into state
useEffect(() => { getWeather(); }, [getWeather])
return (
<div id='page-container'>
<div id='content-wrap' className='Weather'>
{ loading ?
<Spinner /> :
<>
<div className='WeatherHead box mt-3'>
<h4 className='report-head'>Weather Report</h4>
</div>
{/* Current Weather Conditions */}
<h6 className='current-head'>Current Conditions</h6>
<section className='CurrentlyGrid box mt-3'>
/* additional rendering code removed for brevity */
<span><Moment parse='HH:mm'>`${forecast.currently.time}`</Moment></span>
/* additional rendering code removed for brevity */
</section>
</>
}
</div>
</div>
);
};
Weather.propTypes = {
getWeather: PropTypes.func.isRequired,
weather: PropTypes.object.isRequired
};
const mapStateToProps = state => ({ forecast: state.forecast });
export default connect( mapStateToProps, { getWeather } )(Weather);
My React Action Creator
// client/src/redux/actions/weather.js
import axios from 'axios';
import chalk from 'chalk';
// local modules
import {
GET_FORECAST,
FORECAST_ERROR
} from './types';
// Action Creator
export const getWeather = () => async dispatch => {
try {
// get weather forecast
const res = await axios.get(`/api/weather`);
console.log(chalk.yellow('ACTION CREATOR getWeather ', res));
// SUCCESS - set the action -- type = GET_WEATHER & payload = res.data (the forecast)
dispatch({
type: GET_FORECAST,
payload: res.data
});
} catch (err) {
// FAIL - set the action FORECAST_ERROR, no payload to pass
console.log('FORECAST_ERROR ',err)
dispatch({
type: FORECAST_ERROR
});
};
};
My React Reducer
// client/src/redux/reducers/weather.js
import {
GET_FORECAST,
FORECAST_ERROR,
} from '../actions/types'
const initialState = {
forecast: null,
loading: true
}
export default (state = initialState, action) => {
const { type, payload } = action
switch (type) {
case GET_FORECAST:
return {
...state,
forecast: payload,
loading: false
}
case FORECAST_ERROR:
return {
...state,
forecast: null,
loading: false
}
default:
return state
}
}
My Node Route
// server/routes/api/weather.js
const express = require('express');
const axios = require('axios');
const chalk = require('chalk');
const router = express.Router();
// ***** route: GET to /api/weather
router.get('/weather', async (req, res) => {
try {
// build url to weather api
const keys = require('../../../client/src/config/keys');
const baseUrl = keys.darkskyBaseUrl;
const apiKey = keys.darkskyApiKey;
const lat = keys.locationLat;
const lng = keys.locationLng;
const url = `${baseUrl}${apiKey}/${lat},${lng}`;
console.log(chalk.blue('SERVER SIDE ROUTE FORECAST URL ', url));
const res = await axios.get(url);
// forecast -- strip down res, only using currently{} & daily{}
const weather = {
currently: res.data.currently,
daily: res.data.daily.data
};
console.log(chalk.yellow('SERVER SIDE ROUTE FORECAST DATA ', weather));
// return weather
res.json({ weather });
} catch (error) {
console.error(chalk.red('ERR ',error.message));
res.status(500).send('Server Error');
}
});
module.exports = router;
My Express server middleware pertaining to routes (just to be thorough)
// server/index.js
/* code deleted for brevity */
// define routes
app.use('/api/users', require('./routes/api/users'));
app.use('/api/auth', require('./routes/api/auth'));
app.use('/api/weather', require('./routes/api/weather'));
app.use('/api/favorites', require('./routes/api/favorites'));
/* code deleted for brevity */
If the code snippets included are not sufficient, the repo resides here: https://github.com/dhawkinson/TH12-BnBConcierge
Thank you in advance for help with this.
***** Updates *****
I notice that the console logs I have in both actions/weather.js & reducers/weather.js on the client side & routes/api/weather.js on the server side are NOT firing. That tells me that those modules must not be executing. That would explain why I am getting the error "Cannot read property 'currently' of undefined" in client/src/components/pages/functional/Weather.js. Clearly I have a missing link in this chain. I just can't see what it is.
I tried a small refactor, based on input below. I was trying to see if there was some kind of naming conflict going on. this is what I did in my React functional Component:
// client/src/components/pages/functional/Weather.js
...
const mapStateToProps = state => ({weather: { forecast: state.forecast, loading: state.loading }});
...
It didn't help.
I see that in your combineReducers here you are setting as
export default combineReducers({
alert,
auth,
weather
})
So in the store, things gets saved as { alert: {...}, auth: {...}, weather: {...}}. Can you try accessing the forecast value in your Weather as state.weather.forecast ?
const mapStateToProps = state => ({ forecast: state.weather.forecast });
Let me know if it works.
You need to modify your component.
const dispatch = useDispatch();
useEffect(() => { dispatch(getWeather()); }, [getWeather])
And your mapToStateToProps should be as follows:
const mapStateToProps = state => ({ forecast: state.weather.forecast });
I have an API build using sailsjs and a react redux attach to a nodejs backend, and i am trying to implement socket.io for a realtime communication, how does this work?
is it
socket.io client on the react side that connects to a socket.io server on its nodejs backend that connects to a socket.io server on the API
socket.io client on the react side and on its nodejs backend that connects to a socket.io server on the API
i have tried looking around for some answers, but none seems to meet my requirements.
to try things out, i put the hello endpoint on my API, using the sailsjs realtime documentation, but when i do a sails lift i got this error Could not fetch session, since connecting socket has no cookie (is this a cross-origin socket?) i figure that i need to pass an auth code inside the request headers Authorization property.
Assuming i went for my #1 question, and by using redux-socket.io,
In my redux middleware i created a socketMiddleware
import createSocketIoMiddleware from 'redux-socket.io'
import io from 'socket.io-client'
import config from '../../../config'
const socket = io(config.host)
export default function socketMiddleware() {
return createSocketIoMiddleware(
socket,
() => next => (action) => {
const { nextAction, shuttle, ...rest } = action
if (!shuttle) {
return next(action)
}
const { socket_url: shuttleUrl = '' } = config
const apiParams = {
data: shuttle,
shuttleUrl,
}
const nextParams = {
...rest,
promise: api => api.post(apiParams),
nextAction,
}
return next(nextParams)
},
)
}
and in my redux store
import { createStore, applyMiddleware, compose } from 'redux'
import createSocketIoMiddleware from 'redux-socket.io'
...
import rootReducers from '../reducer'
import socketMiddleware from '../middleware/socketMiddleware'
import promiseMiddleware from '../middleware/promiseMiddleware'
...
import config from '../../../config'
export default function configStore(initialState) {
const socket = socketMiddleware()
...
const promise = promiseMiddleware(new ApiCall())
const middleware = [
applyMiddleware(socket),
...
applyMiddleware(promise),
]
if (config.env !== 'production') {
middleware.push(DevTools.instrument())
}
const createStoreWithMiddleware = compose(...middleware)
const store = createStoreWithMiddleware(createStore)(rootReducers, initialState)
...
return store
}
in my promiseMiddleware
export default function promiseMiddleware(api) {
return () => next => (action) => {
const { nextAction, promise, type, ...rest } = action
if (!promise) {
return next(action)
}
const [REQUEST, SUCCESS, FAILURE] = type
next({ ...rest, type: REQUEST })
function success(res) {
next({ ...rest, payload: res, type: SUCCESS })
if (nextAction) {
nextAction(res)
}
}
function error(err) {
next({ ...rest, payload: err, type: FAILURE })
if (nextAction) {
nextAction({}, err)
}
}
return promise(api)
.then(success, error)
.catch((err) => {
console.error('ERROR ON THE MIDDLEWARE: ', REQUEST, err) // eslint-disable-line no-console
next({ ...rest, payload: err, type: FAILURE })
})
}
}
my ApiCall
/* eslint-disable camelcase */
import superagent from 'superagent'
...
const methods = ['get', 'post', 'put', 'patch', 'del']
export default class ApiCall {
constructor() {
methods.forEach(method =>
this[method] = ({ params, data, shuttleUrl, savePath, mediaType, files } = {}) =>
new Promise((resolve, reject) => {
const request = superagent[method](shuttleUrl)
if (params) {
request.query(params)
}
...
if (data) {
request.send(data)
}
request.end((err, { body } = {}) => err ? reject(body || err) : resolve(body))
},
))
}
}
All this relation between the middlewares and the store works well on regular http api call. My question is, am i on the right path? if i am, then what should i write on this reactjs server part to communicate with the api socket? should i also use socket.io-client?
You need to add sails.io.js at your node server. Sails socket behavior it's quite tricky. Since, it's not using on method to listen the event.
Create sails endpoint which handle socket request. The documentation is here. The documentation is such a pain in the ass, but please bear with it.
On your node server. You can use it like
import socketIOClient from 'socket.io-client'
import sailsIOClient from 'sails.io.js'
const ioClient = sailsIOClient(socketIOClient)
ioClient.sails.url = "YOUR SOCKET SERVER URL"
ioClient.socket.get("SAILS ENDPOINT WHICH HANDLE SOCKET", function(data) {
console.log('Socket Data', data);
})