Currently working on a project which is Vue on top of rails 4. I am consuming webhooks from the square API, and I want to be able to get that data into vue so that my data can be updated in real time as square data changes. I've asked this question before, but I'm at a slightly different point in the problem; this is getting down to the nuts and bolts.
Currently, on the server side, I have webhooks setup to fire off to a rails controller, and that works well, i can see that data coming in.
On the client side, I have a socket open and listening to that same rails controller endpoint.
What I'm having trouble with is that even though I can see the webhook hit the controller, and the socket is active, I cant seem to get the socket to pick up on the controller endpoint emitting data. Doesnt seem like the controller endpoint is passing the data along as I expect, and I suspect there is both a gap in my knowledge of rails about how to emit data properly, and how to properly consume it with a socket.
What am I missing to be able to connect the dots here?
Caveats:
I realize this might be possible with ActionCable, and as a last resort I may go with that, but my client is very against upgrading from rails 4 (heavy lift). If this happens to be the only way to do it, so be it, but I want to explore all other options first.
Im no rails expert, and have very little experience with sockets. So this whole approach might, and probably IS foolish. I also may be misunderstanding some parts of how some of these technologies work.
I am unfortunately, bound to using rails and vue.
I am working on localhost, and using ngrok to create a proper URL for the webhooks to hit. I doubt its a problem, but maybe?
I have explored using a node server behind the scenes and sending webhooks directly to that and listening to that server with the socket. Couldnt get that to work either, but tips on how to achieve that if its a good idea are also welcome.
For reference:
Rails Controller:
class WebhooksController < ApplicationController
skip_forgery_protection
def order_update
p request
p params
if request.headers['Content-Type'] == 'application/json'
data = JSON.parse(request.body.read)
else
# application/x-www-form-urlencoded
data = params.as_json
end
render json: data
end
end
Client Code (Vue && Socket):
import { createApp } from 'vue';
import SquareOrders from '../views/SquareOrders.vue';
import VueSocketIO from 'vue-socket.io';
import { io } from "socket.io-client";
const socket = io("http://localhost:3000", {
transports: ["polling", "flashsocket"],
withCredentials: true,
path: '/webhooks/order_update'
});
export default function loadOrdersApp(el, pinia) {
const app = createApp(SquareOrders);
app.use(pinia)
.use(new VueSocketIO({
debug: true,
connection: socket
}))
.mount(el);
}
Suggestions on better approaches are appreciated, as are corrections to my basic knowledge if I am misunderstanding something.
So, after a lot of trial and error, i managed to answer this with a different approach.
as far as I can tell, actually listening to the rails endpoint without something like ActionCable seems impossible, so I booted up and express server to run in the background and ingest incoming webhooks.
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors');
// Create a new instance of express
const app = express()
// Tell express to use the body-parser middleware for JSON
app.use(bodyParser.json())
// ALLOW OUR CLIENT SIDE TO ACCESS BACKEND SERVER via CORS
app.use(cors({
origin: 'http://localhost:3000'
}));
// Tell our app to listen on port 3000
const server = app.listen(8080, function (err) {
if (err) {
throw err
}
console.log('Server started on port 8080')
})
const io = require('socket.io')(server);
app.set('io', io)
// Route that receives a POST request to /webhook
app.post('/webhook', function (req, res, next) {
const io = req.app.get('io');
console.log(req.body)
io.sockets.emit('orderUpdate', req.body)
//res.send(req.body)
res.sendStatus( 200 );
next();
})
io.on('connection', function(socket){
console.log('A connection is made');
});
With that in place, I pointed my webhook to localhost through ngrok, and then added logic in Vue through https://www.npmjs.com/package/vue-socket.io-extended
import { io } from "socket.io-client";
import SquareOrders from '../views/SquareOrders.vue';
import VueSocketIOExt from 'vue-socket.io-extended';
import socketStore from '../stores/sockets.js';
const socket = io("http://localhost:8080", {
transports: [ "websocket", "polling"],
});
export default function loadOrdersApp(el, pinia) {
const app = createApp(SquareOrders);
app.use(pinia)
//.use(socketStore)
.use(VueSocketIOExt, socket)
.mount(el);
}
To then listen for incoming socket emits on the socket.io channel.
With those both in place, i was able to ingest those socket emissions in my frontend app
export default {
name: "SquareOrders",
components: {
Filters,
GlobalLoader,
OrdersList,
FilterGroup,
Filter,
OrderActions
},
// LISTENING FOR THE SOCKETS IN VUE COMPONENT
sockets: {
connect() {
console.log('socket connected in vue')
},
orderUpdate(val) {
console.log(val)
debugger;
console.log('this method was fired by the socket server. eg: io.emit("customEmit", data)')
}
},
mounted() {
const os = orderStore();
os.getOrders();
os.getSourcesList();
os.getSandboxOrders();
console.log(this.$socket)
this.$socket.client.io.on('orderUpdate', (payload) => {
console.log(payload)
})
setInterval(function () {
os.getOrders();
}, 60000);
},
computed: {
...mapState(orderStore, {
orders: store => store.orders,
initalLoad: store => store.flags.loaded,
filterDefs: store => store.filters.definitions,
sandbox: store => store.sandbox
})
},
};
Related
I'm using socket.io to communicate between a server and many clients. Locally everything works fine. However, on production it doesn't work as desired. We use microservices, and this specific microservice is part of a suite of services that run together using webpack module federation.
When accessing the app directly through the k8s ingress, I can see that the clients are connecting to the server. However, clients running through module federation (in the bigger app that consists of my microapp and others), do not connect to the server, and every few seconds a 404 error is printed to the console for a GET request to:
http://<BASE_URL>/socket.io/?EIO=4&transport=polling&t=XXXXXX
where XXXXXX is some random string. I believe that I need to redirect the clients' sockets somehow to reach the server, but I don't know how to do so.
Relevant Client Code
const socket = io.connect("");
socket.on("someEvent", (param) => {
doSomething()
});
Relevant Server Code
let server = app.listen(port, (err) => {
if (err) throw err;
// Express server Ready on http://localhost:port
});
...
let io;
const initSocket = (server) => {
io = require("socket.io")(server, {
cors: { origin: "*" }
});
io.on('connection', (socket) => {
logging.mainLogger.info(`successfully connected to socket
${JSON.stringify(socket.id)}`);
});
io.listen(server);
}
const emitSomeEvent = (param) => {
io.emit("someEvent", param);
}
I'm using nuxt-socket-io along with an Express.js server with socketio as well.
When I start up the client/server, the server-side socket.io connects and console for the server will print "connected").
When I try to connect with nuxt (the client-side part of socket.io), nothing happens. Mounted() is called correctly (the "hm" console log prints out), but the socket never seems to be made. I tried testing this.socket.on('connect-error') and this.socket.on('connect-timeout') for the CLIENT side (the server-side socket.io connects properly), but nothing was ever emitted after about 5 minutes of waiting. The persist: true isn't the issue either; I tried to remove it and had the same issue. I initially didn't have this.socket.open() and had the same problems, so I don't think that line does anything, either.
NuxtJS frontend
mounted() {
console.log("hm");
this.socket = this.$nuxtSocket({
channel: '/profile',
persist: true
})
this.socket.open();
this.socket.on('connection', (socket) => {
console.log("connected")
})
//Listens for the SERVER-EMITTED event 'send'
this.socket.on('send', (message) => {
console.log("client received event!")
console.log(message);
});
},
methods: {
sendMessage() {
// This method IS CALLED correctly with a button (I checked), but the emit is not transmitting
// sends a CLIENT-EMITTED event to the server
this.socket.emit('send-message', {
message: "hey!"
}, (res) => {console.log(res)})
},
nuxt.config.js
io: {
sockets: [{
name: 'main',
default: true,
url: 'http://localhost:3000'
}]
},
My Express Backend (port is 8080)
import express from "express";
import { db } from "./app/config/db.config";
import { authRouter } from "./app/routes/auth.router";
import * as dotenv from "dotenv";
const http = require('http');
const socketio = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketio(server, {
cors: {
origin: '*',
methods: ["GET", "POST"]
}
});
// run on connection to socket
io.on('connection', (socket: any) => {
console.log("connected")
})
// listens for the CLIENT-EMITTED event 'send-message'
io.on('send-message', (message: any) => {
console.log(message + "server received!");
// sends a SERVER-EMITTED event "send" to be received by nuxt client
io.emit('send', "message!")
})
server.listen(process.env.PORT, () => {
console.log(`Server is running on port ${process.env.PORT}`);
});
Axios is also running on port 8080, I don't know if that would cause any issues but I don't get any errors when I try to run my whole program (which includes login/registration with axios).
Anyone know why my events aren't transmitting? Thank you!
In your server code, you're adding your 'send-message' event listener to the io object, which is your main socket.io Server instance. However, event listeners should be added to the socket object you get from the connection event. Something like:
// A new connection comes in
io.on('connection', (socket: Socket) => {
// Now we listen for the event that comes from this particular socket
socket.on('send-message', (message: any) => {
// You also use the Socket instance to send events back to clients, not to the `io` Server.
socket.emit('send', "message!");
});
});
The Socket instance docs have more info on what you can do with these sockets.
I have been fighting with setting this up for longer than I would like to admit.
At first, I was having CORS issues, after following what the socket.io documentation / other stack overflow threads I was getting hit with, GET / POST 400 errors.
Finally after that, I noticed a few threads mention to pass in {transports: ['websocket']} on the server and in the client.
Once I did that, I stopped getting error messages, however, I am still not able to make a connection from my client to my socket.io server. I am hoping I can get some guidance.
I am running Socket.io 3.0 and express 4+
Here is what my server / client looks like at the moment..
SERVER (As an express router)
const express = require('express');
const socketIO = require("socket.io");
const http = require('http');
let app = express();
let router = express.Router();
let server = http.createServer(app);
// The event will be called when a client is connected.
let io = socketIO(server, {transports: ['websocket']})
io.on("connection", socket => {
console.log("connection")
socket.emit("hello", { data: "more data" })
socket.on("disconnect", () => {
console.log("user left")
})
})
server.listen(8081, () => console.log('Socket.io listening on *:8081'));
module.exports = router;
Client (React)
// Socket.IO
import io from 'socket.io-client';
const socket_io = io('localhost:8081', {transports: ['websocket']})
// Socket.io UseEffect
useEffect( () => {
const initSocket = () => {
console.log(socket_io)
socket_io.on("hello", data => {
setSocket(data);
console.log(data);
});
// CLEAN UP THE EFFECT
return () => socket_io.disconnect();
}
initSocket()
},[])
Here is what my Console currently looks like when I log out the socket connection:
So, as embarrassing as this is, the breaking change was that the socket.io-client module in the React client application wasn't 3.0 like the one on the server. Therefore they weren't able to handshake.
My advice, is if you have the CORS rule added or the transport: websocket added, make sure you look at your package.json file in your server / client apps to make sure that the socket.io package version matches.
I'm making a REST API that works with routes and actions like /api/route/action. But I want to add WebSocket functionalities. So I want WebSockets to also be addressable by url.
I have this code:
const socketio = require('socket.io');
//server is a http.createServer()
module.exports = server => {
const io = socketio(server, { route: '/socketapi/test' );
io.on('connection', s => {
s.on('a', () => s.emit('b'));
s.emit('message', 'You connected to /test.');
});
const io2 = socketio(server, { route: '/socketapi/something_else' });
io2.on('connection', s => {
s.on('z', () => s.emit('y'));
s.emit('message', 'Hi');
});
};
The reason why I want to split them is so I don't have to keep track of event names I've already used, and so I can separate the logic in the connection event.
But it seems this is not possible. If I have two socket.io instances running I can't connect to either.
Is this possible or will I have to use some tricks and perhaps an event that the client can send to let me know what it wants to subscribe to?
You can use a built in feature of socket.io called namespaces to achieve this behaviour.
Here is a basic example:
Server side:
const nsp = io.of('/my-namespace');
nsp.on('connection', function(socket){
console.log('someone connected');
});
nsp.emit('hi', 'everyone!');
Client side:
const socket = io('/my-namespace');
Now the client can emit and receive messages which are specific to a namespace. With the use of namespaces your problem of name conflicts of the events, will be solved.
I am currently making a backend server that uses Express and Websockets in Typescript. From a high level, my issue is that I would like to make it so that when I receive a POST to one of my routes, the handler for that route sends a message to a client websocket. I currently have three files: app.ts, conversationController.ts, and server.ts.
The way that I've structured it is that conversationController.ts handles the http and websocket logic for my route, app.ts uses that controller and exports my express app, and server.ts is what creates both the HTTP server and Websocket server. Here's my code so you can get a feel for what is going on and what I'm trying to do. As I'm sure you'll see, I am doing things in a bit of a funky way; I would love to get feedback on how I can structure things better/best practices, but either way, my code right now does work. There is a bit more explained at the bottom.
Here's the important stuff from conversationController.ts
import { NextFunction, Response, Request, Router, } from "express";
export class ConversationController {
public router: Router;
constructor() {
this.router = Router();
this.setupRoutes();
}
private handleMessageFromAndroid(req: Request, res: Response, next: NextFunction) {
const message = {
phoneNumber: req.body.phoneNumber,
textMessageBody: req.body.textMessageBody,
};
/* here is where I would somehow try to have access to the websocket connection that is made below in socketLogic(), so that I can tell the client of the socket that the backend has received data */
/* ideally it would look something like this: */
// ws.send("Received a POST call to the server, sending the data upstream to you, the client websocket on the front-end.");
res.sendStatus(200);
}
private setupRoutes(): void {
this.router.post("/", this.handleMessageFromAndroid.bind(this));
}
public socketLogic(wss: Server): void {
wss.on("connection", (ws) => {
console.log("Connection made to client websocket.");
/* once again, I would like to somehow have access to this "ws" object up in handleMessageFromAndroid*/
ws.on("message", (data: string) => {
const message = JSON.parse(data);
console.log("Received message from client: ", message);
ws.send("Thanks for sending a message to the server.");
});
});
}
}
Here's the important stuff from app.ts
import * as express from "express";
import { ConversationController } from "./controllers/conversation";
export const app = express();
app.use("/texts", new ConversationController().router);
Finally, here's server.ts
import * as http from "http";
import * as WebSocket from "ws";
import { app } from "./app";
import { ConversationController } from "./controllers/conversation";
const server = app.listen(app.get("port"), () => {
console.log("App is running at http://localhost:%d in %s mode", app.get("port"), app.get("env"));
});
export const wss = new WebSocket.Server({ server });
new ConversationController().socketLogic(wss);
Now, let me just be very clear. This code does work. I can connect to the WebSocket server on my front-end; I receive the message saying that I connected, and I can send messages back and forth through the socket. HOWEVER, my issue is that I would like to somehow make the opened socket connection (in the socketLogic() method; the socket called "ws") available to the handleMessageFromAndroid method in conversationController.ts.
At first, I was thinking to import the WebSocket server (the one created in server.ts) into conversationController.ts and just make it an instance variable so that the server would be available to the entire class and that would have solved all of my problems, but that led me to find out about cylic dependencies. Basically, when I tried that, the WebSocket server from server.ts is undefined since conversationController.ts gets compiled before server.ts.
I'm not sure exactly how to solve this issue, or if it can even be done. But I would definitely appreciate some help as I've been working on solving this issue for a few days now and have checked many SO questions and Googled quite a bit. Thanks.