Using socket.io with React and Google App Engine - node.js

I've created a Node(express)/React app that uses socket.io and Redux's store as follows:
import io from "socket.io-client";
import * as types from "../actions/types";
import { cancelReview, startReview } from "./actions";
const socket = io("http://localhost:8080", {
transports: ["websocket"]
});
export const init = store => {
socket.on("connect", () => {
console.log("websocket connection successful...");
socket.on("cancelReview", (id, name) => {
cancelReview(store, id, name);
});
socket.on("startReview", (id, name) => {
startReview(store, id, name);
});
});
};
This function is then called from store.js as follows:
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension/developmentOnly";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
import { init } from "./socket/socket";
// Initial state
const initialState = {};
// Middleware
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
init(store);
export default store;
Everything works fine on my local machine, but I'm now realizing after doing some research that this will not work on Google's app engine because instead of http://localhost:8080 I need to get the actual IP address from Google's metadata server and pass in EXTERNAL_IP + ":65080". So I'm able to get the external IP in my express app as follows:
const METADATA_NETWORK_INTERFACE_URL =
"http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip";
function getExternalIp(cb) {
const request = axios.create({
baseURL: METADATA_NETWORK_INTERFACE_URL,
headers: { "Metadata-Flavor": "Google" }
});
request
.get("/", (req, res) => {
return cb(res.data);
})
.catch(err => {
console.log("Error while talking to metadata server, assuming localhost");
return cb("localhost");
});
}
However, if I pass this value into my render function as seen below, React creates a prop to pass into components (as far as I understand from the info I could find):
app.get("*", (req, res) => {
getExternalIp(extIp => {
res.render(path.resolve(__dirname, "client", "build", "index.html"), {
externalIp: extIp
});
});
I am not able to access this value via the window global. So my question is, how do I access this external IP from my store initialization, since it is not an actual React component?
Thanks in advance.

Related

How to override url for RTK query

I'm writing pact integration tests which require to perform actual call to specific mock server during running tests.
I found that I cannot find a way to change RTK query baseUrl after initialisation of api.
it('works with rtk', async () => {
// ... setup pact expectations
const reducer = {
[rtkApi.reducerPath]: rtkApi.reducer,
};
// proxy call to configureStore()
const { store } = setupStoreAndPersistor({
enableLog: true,
rootReducer: reducer,
isProduction: false,
});
// eslint-disable-next-line #typescript-eslint/no-explicit-any
const dispatch = store.dispatch as any;
dispatch(rtkApi.endpoints.GetModules.initiate();
// sleep for 1 second
await new Promise((resolve) => setTimeout(resolve, 1000));
const data = store.getState().api;
expect(data.queries['GetModules(undefined)']).toEqual({modules: []});
});
Base api
import { createApi } from '#reduxjs/toolkit/query/react';
import { graphqlRequestBaseQuery } from '#rtk-query/graphql-request-base-query';
import { GraphQLClient } from 'graphql-request';
export const client = new GraphQLClient('http://localhost:12355/graphql');
export const api = createApi({
baseQuery: graphqlRequestBaseQuery({ client }),
endpoints: () => ({}),
});
query is very basic
query GetModules {
modules {
name
}
}
I tried digging into customizing baseQuery but were not able to get it working.

How to fetch data in ReactJS from NodeJS API for Influxdb?

I am using InfluxDB as database, I used influxdb-nodejs library module to create API to write data and Query data from Influxdb.
The Queryfunction.js API code is as follows:
module.exports = {
queryfunc: function() {
const Influx = require('influxdb-nodejs');
const client = new Influx('http://127.0.0.1:8086/mydb');
client.query('http')
.where('type', '2')
.then(console.info)
.catch(console.error);
}
}
I use a script.js file to call queryfunc() in Queryfunction.js API:
const myModule = require('./Queryfunction');
let val = myModule.queryfunc();
I use command node script to run the script file.
The result is an array
C:\Users\Admin\Desktop\reactApp\API>node script
{ results: [ { statement_id: 0, series: [Array] } ] }
I am using ReactJS to create front end UI components. How to fetch the resultant array data in ReactJS?
You're either need to write to a json file or you'll need to have an express wrapper around your db calls.
const express = require('express')
const app = express();
app.get('/api', (req, res, next) => {
const Influx = require('influxdb-nodejs');
const client = new Influx('http://127.0.0.1:8086/mydb');
client.query('http')
.where('type', '2')
.then(data => res.json(data)
.catch(err => next(err)); // error middleware to handle
}
app.listen('3000', () => console.log('running on http://localhost:3000'))
Within react you do a fetch:
Class App extends React.Component {
componentDidMount() {
fetch('http://localhost:3000')
.then(res => res.json())
.then(data => this.setState( {data} ) )
render() {
....
}
}

Injecting App as a parameter vs Import App from server.js in loopBack framework

I'm using loopback3 and I'm thinking about optimising the codebase now, what is the point of injecting the application object as a parameter to getUser method from afterRemote hook https://loopback.io/doc/en/lb2/Remote-hooks.html, if I can access loopback instance by requiring it directly inside userService.ts.
I feel that I'm missing something important here, I tried to summarise below
A. For me importing is better since I will have less code and, there
will be no need to inject the app each time.
B. Injected and Imported
objects are equal, _.isEqual(app, App)
C. I've checked the
performance with process.hrtime() and got same results.
app/models/activity.ts
import {UserService} from 'app/service/userService';
import {Attach} from 'app/models/remote/activityRemote';
export = function (Activity) {
Activity.afterRemote('find', function (ctx, result, next) {
UserService.getUser(Activity.app, Activity.username)
.then(() => next())
.catch(next);
});
/**
* attach remote
*/
Attach(Activity);
};
userService.ts
import {Server} from 'app/server/interface/server';
import * as App from 'app/server/server';
import * as _ from 'lodash';
/**
* #class UserService
*/
export class UserService {
/**
* get user's public profile
* #param {Server} app loopback app
* #param {string} username
* #returns {Promise<User>}
*/
public static getUser(app: Server, username: string): Promise<User> {
return App.models.user.findOne(filter) // Equal and
return app.models.user.findOne(filter) // does the same
.then((user: User) => {
if (!user) {
return Promise.reject(ServerError.get('User not found', 404));
}
return Promise.resolve(user);
});
}
}
server.ts
import {Server} from 'app/server/interface/server';
import * as loopback from 'loopback';
import * as boot from 'loopback-boot';
let App: Server = loopback();
module.exports = App;
export = App;
App.start = () => {
return App.listen(() => {
const baseUrl = App.get('url').replace(/\/$/, '');
App.emit('started');
console.log('Web server listening at: %s', baseUrl);
if (App.get('loopback-component-explorer')) {
console.log(
'Browse your REST API at %s%s',
baseUrl,
App.get('loopback-component-explorer').mountPath
);
}
});
};
boot(App, __dirname, (err: Error) => {
if (err) {
throw err;
}
if (require.main === module) {
App.start();
}
});
Unless you have multiple loopback applications in the same process going around, then you have no reason to pass the application as a parameter. Just import the app as it's more readable and cleaner.
Also you don't need to use Promise.resolve when you already have a promise:
return app.models.user.findOne(filter) // does the same
.then((user: User) => {
if (!user) {
throw ServerError.get('User not found', 404);
}
return user;
});
This will have the same effect.

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

localhost REST API request error ionic2 angular2

I am making a get/post request to my locally hosted REST API server in an Ionic 2 app. The errow below shows up afer a couple of seconds.
3 387557 group EXCEPTION: Response with status: 0 for URL: null
4 387558 error EXCEPTION: Response with status: 0 for URL: null
5 387558 groupEnd
6 387568 error Uncaught Response with status: 0 for URL: null, http://localhost:8100/build/js/app.bundle.js, Line: 88826
I am able to make a successful curl request to the local server. Here is my code for reference.
app.js
var express = require("express");
var mysql = require("mysql");
var bodyParser = require("body-parser");
var SHA256 = require("sha256");
var rest = require("./REST.js");
var app = express();
function REST(){
var self = this;
self.connectMysql();
};
REST.prototype.connectMysql = function() {
var self = this;
var pool = mysql.createPool({
connectionLimit : 100,
host : 'host',
user : 'user',
password : 'password',
database : 'database',
debug : false
});
pool.getConnection(function(err,connection){
if(err) {
self.stop(err);
} else {
self.configureExpress(connection);
}
});
}
REST.prototype.configureExpress = function(connection) {
var self = this;
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
var router = express.Router();
app.use('/api', router);
var rest_router = new rest(router,connection,SHA256);
self.startServer();
}
REST.prototype.startServer = function() {
app.listen(3000, function() {
console.log("All right ! I am alive at Port 3000. OKAY BUDDY");
});
}
REST.prototype.stop = function(err) {
console.log("ISSUE WITH MYSQL n" + err);
process.exit(1);
}
new REST();
REST.js
var mysql = require("mysql");
function REST_ROUTER(router, connection, SHA256) {
var self = this;
self.handleRoutes(router, connection, SHA256);
}
REST_ROUTER.prototype.handleRoutes= function(router,connection,SHA256) {
router.get("/",function(req,res){
res.json({'foo': 'bar'});
});
});
login.js (component)
import {Component} from '#angular/core';
import {NavController} from 'ionic-angular';
import {AuthProvider} from '../../providers/auth/auth';
/*
Generated class for the LoginPage page.
See http://ionicframework.com/docs/v2/components/#navigation for more info on
Ionic pages and navigation.
*/
#Component({
templateUrl: 'build/pages/login/login.html',
providers: [AuthProvider]
})
export class LoginPage {
static get parameters() {
return [[NavController], [AuthProvider]];
}
constructor(nav, AuthProvider) {
this.nav = nav;
this.authProvider = AuthProvider;
this.form = {};
}
login(form) {
this.authProvider.login(form).then(res => {
alert(JSON.stringify(res));
});
}
}
auth.js (provider)
import {Injectable} from '#angular/core';
import {Http, Headers, RequestOptions} from '#angular/http';
import 'rxjs/add/operator/map';
/*
Generated class for the Auth provider.
See https://angular.io/docs/ts/latest/guide/dependency-injection.html
for more info on providers and Angular 2 DI.
*/
#Injectable()
export class AuthProvider {
static get parameters(){
return [[Http]]
}
constructor(http) {
this.url = 'http://localhost:3000/api';
this.http = http;
}
login(form) {
return new Promise(resolve => {
this.http.get(this.getUrl)
.map(res => res.json())
.subscribe(data => {
resolve(data);
});
});
}
}
I had the same problem, and was able to resolve it. I was serving my API on localhost:8000. When ionic makes a request to localhost or 127.0.0.1, I think it is blocked. I instead found my computer's IP address and hosted my webserver on 0.0.0.0:8000 and instead of hitting http://localhost:8000/api/my/endpoint I hit http://mycomputerip:8000/api/my/endpoint, and it worked!
You are trying to request empty URL bacause of typo in auth.js login function:
this.http.get(this.getUrl)
this.getUrl is not defined in your code samples. Easy fix:
this.http.get(this.url)

Resources