Can I make API calls to my Node server that is connected through Web Socket with my ReactJS? - node.js

I have front-end in ReactJS where I am integrating my backend in NodeJS through websocket(also using sharedb).
https://github.com/share/sharedb
My setup for this is below:
import sharedb from 'sharedb/lib/client';
// Using a Reconnecting Web Socket https://github.com/share/sharedb/pull/138/files
import { default as WebSocket } from 'reconnecting-websocket';
export default class ShareService {
constructor(location) {
this.socket = new WebSocket(location);
// Open WebSocket connection to ShareDB server
const connection = new sharedb.Connection(this.socket);
this.connection = connection;
this.location = location;
}
close() {
this.connection.close();
}
reconnect() {
this.socket = new WebSocket(this.location);
this.connection.bindToSocket(this.socket);
}
doc(collection, id) {
const doc = this.connection.get(collection, id);
return doc;
}
sendCommand(data) {
this.socket.send(JSON.stringify({
'a': 'command',
'deviceType': 'web',
...data
}));
}
}
And when I need to call from React I do:
this.shareService.sendCommand({
command: 'I_AM_COMMAND',
payload: {
activeAgendaId: 123,
}
});
I want to know if I can also make normal API calls to my Node from React even after WebSocket connection is there?

Related

Bi-directional Websocket with RTK Query

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?

Cannot get data from an Node API

I have an API (Localhost:3000) using node and a front end (Localhost:4200) using Angular 6. Using my regular chrome browser, I am able to CRUD to the database in the API but when I use the android emulator using (10.0.2.2:4200), I cannot do any of the CRUD to the database anymore. Please see my codes below:
Node [index.js]
const express = require("express");
const nedb = require("nedb");
const rest = require("express-nedb-rest");
const cors = require("cors");
const app = express();
const datastore = new nedb({
filename: "mycoffeeapp.db",
autoload: true
});
const restAPI = rest();
restAPI.addDatastore('coffees', datastore);
app.use(cors());
app.use('/', restAPI);
app.listen(3000);
angular front end
This is in the data.service
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
#Injectable({
providedIn: "root"
})
export class DataService {
public endpoint = "http://localhost:3000";
constructor(
private http: HttpClient
) {}
getList(callback) {
this.http.get(`${this.endpoint}/coffees`)
.subscribe(response => {
callback(response);
});
}
get(coffeeId: string, callback) {
this.http.get(`${this.endpoint}/coffees/${coffeeId}`)
.subscribe(response => {
callback(response);
});
}
save(coffee, callback) {
if (coffee._id) {
this.http.put(`${this.endpoint}/coffees/${coffee._id}`, coffee)
.subscribe(response => {
callback(true);
});
} else {
this.http.post(`${this.endpoint}/coffees`, coffee)
.subscribe(response => {
callback(true);
});
}
}
}
in the component:
constructor(
private data: DataService,
private router: Router,
private gls: GeoLocationService
) { }
ngOnInit() {
this.data.getList(list => {
this.list = list;
});
}
If you run an emulated android device and try to access your development environment environment on 10.0.2.2:4200, you'll be able to reach the angular app provided that th emulator is on the same network.
Now, you need to make sure that your API is reachable from outside of your local machine, and, in your angular front, set the API url using an IP address
export class DataService {
public endpoint = "http://10.0.2.2:3000";
If you use localhost, this will point to the emulated device itself, which does not have you API runnnig

Receive Nodejs events in React

I am working on a project that will use React as my client and Nodejs as my server. My design is that the Nodejs server will listen to some external data streams, process the data, save the data in MongoDB and then emit some events to React. The server-side code is like
const EventEmitter = require('events');
const WebSocket = require('ws');
const myEmitter = new EventEmitter();
const ws = new WebSocket('wss://someurl');
ws.on('message', (data) => {
........
/*
preprocess and do the mongodb stuff
*/
myEmitter.emit('someevent', data)});
});
My question is, how can I listen for such an event in my React client? If I stick with this approach, do I need to pass in myEmitter to my React components?
I am new to React so please let me know if there is any better way to solve the problem.
do I need to pass in myEmitter to my React components?
no... your client side and serverside code should be separate. You can use a client-side SocketIO app like socket.io.
if you're going to be listening for a bunch of different events in different components, consider using an enhancer style wrapper
function withSocket (event?, onEvent?) { // note: this is TS
return (Component) => {
class WithSocketEvent extends Component {
constructor (props) {
super(props)
this.socket = io.connect(SOCKET_ENDPOINT)
}
componentDidMount () {
if (event && onEvent) {
this.socket.on(event, onEvent)
}
}
componentWillUnmount () {
this.socket && this.socket.close()
}
render () {
return (
<Component
{ ...this.props }
socket={ this.socket }
/>
)
}
}
return WithSocketEvent
}
}
// usage
class HasSocketEvent extends Component {
componentDidMount () {
// handle the event in the component
this.props.socket.on("someEvent", this.onSocketEvent)
}
onSocketEvent = (event) => {
}
render () {
}
}
// handle the event outside the component
export default withSocket("someEvent", function () {
// so something
})(HasSocketEvent)
// or
export default withSocket()(HasSocketEvent)

Angular 5 socket.io not recieving update in other component

Context: I have an angular application with a backend in nodejs. I have a feed that will update when I recieve a message from the server. When new data is inserted the server is notified, but my other component does not recieve anything. I have implemented the socket in a service that is injected into both components.
My server is build like this:
const port = 3000;
const server = require('http').Server(app);
const io = require('socket.io')(server);
io.on('connection', (socket) => {
console.log('New Connection..')
socket.on('action', (data) => {
switch(data) {
case 'new_odds':
socket.emit('refresh_odds', 'UPDATE FEED! (FROM SERVER)')
break;
case 'new_results':
break;
}
});
});
//listen on port omitted
My service in angular:
const SERVER_URL = 'http://localhost:3000';
#Injectable()
export class SocketService {
constructor() {
}
private socket;
public initSocket(): void {
this.socket = socketIo(SERVER_URL);
}
public disconnectSocket(): void {
this.socket.disconnect();
}
public send(action: Action): void {
this.socket.emit('action', action);
}
public onOddsMessage(): Observable<string> {
return new Observable<string>(observer => {
this.socket.on('refresh_odds', (data:string) => {
observer.next(data)
});
});
}
public onEvent(event: Event): Observable<any> {
return new Observable<Event>(observer => {
this.socket.on(event, () => observer.next());
});
}
}
My feed component uses the socket service to listen for emits:
constructor(private _socket : SocketService) {
}
ngOnInit() {
this.initIoConnection();
}
private initIoConnection(): void {
this._socket.initSocket();
this.ioConnection = this._socket.onOddsMessage()
.subscribe((data: string) => {
console.log('Recieved data from oddsMessage')
//this.loadBetFeed();
});
}
In a different component also using the service I'm trying to emit to the socket on the server. It does recieve the message on the server and emit a new message but my feed component does NOT pick up on this
testSocket() {
//NOTIFY SERVER THAT IT SHOULD TELL CLIENTS TO REFRESH
console.log('Test Socket Clicked')
this._socket.initSocket();
this._socket.send(Action.ODDS);
}
I don't understand what I'm doing wrong - I am using a shared service. Even if the components use different socket connections it shouldn't matter since they're listening for the same emits? I've tested in 2 browser tabs and also in incognito. Any help is appreciated!
The issue was I was emitting to the socket instead of the server which means only the current connection could see it.
New server:
io.on('connect', (socket) => {
console.log('Connected client on port %s.', port);
socket.on('action', (data) => {
io.emit('refresh_odds', 'UPDATE FEED! (FROM SERVER)') <-- changed socket to io
});
}

socket io on sails js as API and node+react as Frontend

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);
})

Resources