How to subscribe to stream in Angular2? - node.js

I'm having my streaming web-service running on localhost:8080/stream, which response when any new message added to subscribed mqtt stream. I want to consume this web-service in my Angular2 app. I'm using RxJS to consume NodeJS APIs in Angular2. I tried following code which calls localhost:8080/stream once and ends response. I want my observable to listen continuously to web-service.
var headers = new Headers();
headers.append('Access-Control-Allow-Origin', '*');
headers.append('Content-Type', 'application/json');
let options = new RequestOptions({ headers: headers }); // Create a request option
return this.http.get("http://localhost:8080/stream", options) // ...using post request
.map((res: Response) => res.json()) // ...and calling .json() on the response to return data
.catch((error: any) => Observable.throw(error.json().error));

If I understand your question right, you want to consume data from stream where new messages arrive at some period of time.
To achieve this You need add subscribe to the service.
return this.http.get("http://localhost:8080/stream", options) // ...using post request
.map((res: Response) => res.json()) // ...and calling .json() on the response to return data
.catch((error: any) => Observable.throw(error.json().error)
.subscribe(result => this.result =result));
Result will be updated as new data arrives, and you can use it the way want.
Note: It is best practice to make http calls separate in services and subscribe the service in your component.
For your reference I am adding an example I have worked on for demo purpose.
Create a service for http calls
#Injectable()
export class JsonPlaceHolderService{
constructor(private http:Http){}
getAllPosts():Observable<Post[]>{
return this.http.get("https://jsonplaceholder.typicode.com/posts")
.map(res=>res.json())
}
}
From your component call service and listen to changes continuously.
export class PostsComponent implements OnInit{
constructor(private _service:JsonPlaceHolderService){}
jphPosts:Post[];
title:string="JsonPlaceHolder's Post data";
ngOnInit():void{
this._service.getAllPosts().subscribe(
(data) =>this.jphPosts = data,
(err) => console.log(err),
()=>console.log("service call completed")
);
}
}

You should use websocket on angular and make it listen to your service URL after that you should listen to its events (open, close, message) and then create your own Subject stream using Rxjs to push the new data to the subscribers.
Please check the URL below:
https://medium.com/#lwojciechowski/websockets-with-angular2-and-rxjs-8b6c5be02fac

Streaming data from nodejs to angular with socket.io
This is something that would have been of great use when I was trying to do this. Following contains code from a socket.io package for angular credit goes to original author. This is taken from a working solution and may need some tweaking.
Server side
var app = express(),
http = require('http'),
ioServer = require('socket.io');
var httpServer = http.createServer(app);
var io = new ioServer();
httpServer.listen(1337, function(){
console.log('httpServer listening on port 1337');
});
io.attach(httpServer);
io.on('connection', function (socket){
console.log(Connected socket ' + socket.id);
});
//MQTT subscription
client.on('connect', function () {
client.subscribe(topic, function () {
console.log("subscribed to " + topic)
client.on('message', function (topic, msg, pkt) {
io.sockets.emit("message", {topic: topic, msg: msg, pkt: pkt});
});
});
});
Client Side
Create a customService in angular with following
import * as io from 'socket.io-client';
declare
private ioSocket: any;
private subscribersCounter = 0;
inside service class
constructor() {
this.ioSocket = io('socketUrl', {});
}
on(eventName: string, callback: Function) {
this.ioSocket.on(eventName, callback);
}
removeListener(eventName: string, callback?: Function) {
return this.ioSocket.removeListener.apply(this.ioSocket, arguments);
}
fromEvent<T>(eventName: string): Observable<T> {
this.subscribersCounter++;
return Observable.create((observer: any) => {
this.ioSocket.on(eventName, (data: T) => {
observer.next(data);
});
return () => {
if (this.subscribersCounter === 1) {
this.ioSocket.removeListener(eventName);
}
};
}).share();
}
In your component, Import customService as service
service.on("message", dat => {
console.log(dat);
});

Related

Socket.io socket listener not updating in nextjs api

I have a Next.js project that has the simplest Socket.IO implementation set up. Below is the code.
// pages/index.tsx
let socket: Socket;
const Home: NextPage = () => {
useEffect(() => {
async function socketInit() {
//start server
await fetch("http://localhost:3000/api/socket");
// connects to the socket
socket = io();
socket.on("connect", () => {
console.log("hello");
});
}
socketInit();
}, []);
return (
<button
onClick={() => {
socket.emit("test");
}}
>
hello
</button>
);
};
// pages/api/socket.ts
export default function handler(
req: NextApiRequest,
res: Res
) {
if (res.socket.server.io) { res.end(); return; }
const io = new IOServer(res.socket.server);
res.socket.server.io = io;
io.on('connection', socket => {
socket.on('test', () => {
console.log("1"); //Changing it to "2" doesn't do anything until dev is restarted.
});
});
res.end();
}
For some reason, the listener in the server would not update from hot reload. Restarting the dev is the only way. Why is that?
I think there are 2 issues here:
The response object that you instantiate the IOServer on is not recreated after a HMR, it is still referring to the callback function that prints 1, which is hanging around in memory somewhere.
To fix this, you need to actively call the handler method, unsubscribe the old callback function and resubscribe the new (replaced) callback function. Just interacting with through the socket is not enough. Unfortunately, all tutorials I have seen call the handler socket, which is misleading. It should be called setup-socket-handler instead. What it does is retrieve the actual server from the response object of this handler and register a IOSocket server with the underlying server, which will then register a new handler/endpoint /socket.io that will be used for the communication between client and server.
Here is what I came up with. This should not be used as is in production (make sure the replacement happens only once in production, as it did in the original):
const SocketHandler = (
req: NextApiRequest,
res: NextApiResponseWithSocket
): void => {
if (res.socket.server.io != null) {
logger.info("Socket is already running");
res.socket.server.io.removeAllListeners("connection");
res.socket.server.io.on("connection", onConnection);
} else {
logger.info("Socket is initializing");
const io = new Server<ClientToServerEvents, ServerToClientEvents>(
res.socket.server
);
io.engine.on("connection_error", (err: unknown) => {
logger.error(`Connection error: ${err}`);
});
res.socket.server.io = io;
io.on("connection", onConnection);
}
res.end();
};
After changing the callback function and nextjs doing its HMR, it is required to call the handler once as described in 2. I do this by reloading my frontend page which sends a request to the socket handler.

Websockets with RTK Query configuration issues

I am trying to implement a Websocket connection from a React TypeScript app using RTK query. At the moment I am just trying to connect to a local socket.io server BUT ultimately it will be an AWS API Gateway with Cognito auth. In any case I having some problems getting this to work as a simple starting point. I have a few elements at play that may be causing the issue/s:-
MSW is being used to intercept http requests to mock a restful API locally. I wonder if this is one of the issues
I am adding the Websocket as a query to an RTK Query createApi object with other queries and mutations. In reality the Websocket query will need to hit a different API Gateway to the one that is being set as the baseQuery baseUrl currently. Do I need to create a new and separate RTK Query api using createApi() for the Websocket query?
Anyhow, here is the server code:-
// example CRA socket.io from https://github.com/socketio/socket.io/blob/main/examples/create-react-app-example/server.js
const getWebsocketServerMock = () => {
const io = require('socket.io')({
cors: {
origin: ['http://localhost:3000']
}
});
io.on('connection', (socket: any) => {
console.log(`connect: ${socket.id}`);
socket.on('hello!', () => {
console.log(`hello from ${socket.id}`);
});
socket.on('disconnect', () => {
console.log(`disconnect: ${socket.id}`);
});
});
io.listen(3001);
setInterval(() => {
io.emit('message', new Date().toISOString());
}, 1000);
console.log('Websocket server file initialised');
};
getWebsocketServerMock();
export {};
My RTK Query api file looks like this:-
reducerPath: 'someApi',
baseQuery: baseQueryWithReauth,
endpoints: (builder) => ({
getWebsocketResponse: builder.query<WebsocketResult, void>({
query: () => ``,
async onCacheEntryAdded(arg, { updateCachedData, cacheDataLoaded, cacheEntryRemoved }) {
try {
// wait for the initial query to resolve before proceeding
await cacheDataLoaded;
const socket = io('http://localhost:3001', {});
console.log(`socket.connected: ${socket.connected}`);
socket.on('connect', () => {
console.log('socket connected on rtk query');
});
socket.on('message', (message) => {
console.log(`received message: ${message}`);
// updateCachedData((draft) => {
// draft.push(message);
// });
});
await cacheEntryRemoved;
} catch {
// no-op in case `cacheEntryRemoved` resolves before `cacheDataLoaded`,
// in which case `cacheDataLoaded` will throw
}
}
}),
getSomeOtherQuery(.....),
getSomeOtherMutation(....),
Any advice or thoughts would be greatly appreciated! I guess my main question is should I be able to combine the websocket query in the same createApi function with other queries and mutations that need to use a different baseQuery url as they need to hit different API Gateways on AWS?
Much thanks,
Sam
You can circumvent the baseQuery from being used by specifying a queryFn instead of query on your endpoint.
In the most simple version, that just returns null as data so you can modify it later - but if you have an initial websocket request you can also do that in the queryFn.
queryFn: async () => { return { data: null } },

HTTP2 push for Express

I'm trying to set up HTTP2 for an Express app I've built. As I understand, Express does not support the NPM http2 module, so I'm using SPDY. Here's how I'm thinking to go about it-I'd appreciate advice from people who've implemented something similar.
1) Server setup-I want to wrap my existing app with SPDY, to keep existing routes. Options are just an object with a key and a cert for SSL.
const app = express();
...all existing Express stuff, followed by:
spdy
.createServer(options, app)
.listen(CONFIG.port, (error) => {
if (error) {
console.error(error);
return process.exit(1)
} else {
console.log('Listening on port: ' + port + '.')
}
});
2) At this point, I want to enhance some of my existing routes with a conditional PUSH response. I want to check to see if there are any updates for the client making a request to the route (the client is called an endpoint, and the updates are an array of JSON objects called endpoint changes,) and if so, push to the client.
My idea is that I will write a function which takes res as one of its parameters, save the endpoint changes as a file (I haven't found a way to push non-file data,) and then add them to a push stream, then delete the file. Is this the right approach? I also notice that there is a second parameter that the stream takes, which is a req/res object-am I formatting it properly here?
const checkUpdates = async (obj, res) => {
if(res.push){
const endpointChanges = await updateEndpoint(obj).endpointChanges;
if (endpointChanges) {
const changePath = `../../cache/endpoint-updates${new Date().toISOString()}.json`;
const savedChanges = await jsonfile(changePath, endpointChanges);
if (savedChanges) {
let stream = res.push(changePath, {req: {'accept': '**/*'}, res: {'content-type': 'application/json'}});
stream.on('error', function (err) {
console.log(err);
});
stream.end();
res.end();
fs.unlinkSync(changePath);
}
}
}
};
3) Then, within my routes, I want to call the checkUpdates method with the relevant parameters, like this:
router.get('/somePath', async (req, res) => {
await checkUpdates({someInfo}, res);
ReS(res, {
message: 'keepalive succeeded'
}, 200);
}
);
Is this the right way to implement HTTP2?

Using socketio with react redux

Im building a small chat application using react,redux,socketio and node with mongoose. Normally redux flows through actions (which makes API calls and receive data) and dispatch the data. But in my case the socket will emit to a certain event but it would not return data until we manually emit the data from the back-end. so to achieve the proper redux flow should i add a socket event on actions to retrieve the data (coming from back-end) and then dispatch it or is there any other proper way to achieve this?
Here is a sample code of what i'm planing to do in
Actions file
function sendMessage(data) {
return {
type: SEND_MESSAGE,
payload: data
};
}
export const sendNewMessage = (socket,data) => {
return dispatch => {
socket.emit("send message",data);
socket.on("new message",function(data){
dispatch(sendMessage(data));
});
};
};
That seems perfectly reasonable to me. I would suggest using thunk's "extra argument" for this such that your components do not need to know about the actual socket object:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ socket }))
)
export const sendNewMessage = (data) =>
(dispatch, getState, { socket }) => {
socket.emit("send message", data)
socket.on("new message", (data) => {
dispatch(sendMessage(data))
})
}

How to send response back to client using socket.io?

I have socket.emit call from client to server in response i want to have filename to the client that is not happening with below code not sure what is implemented wrong any idea, I do not see any error. How can i get response fro server using socket.emit ?
client.js
socket.emit('startRecording',function (response) {
console.log('start recording emit response',response);
});
server.js
socket.on('startRecording',function () {
var response;
logsRecording(function (filename) {
response = filename;
return response;
//socket.emit('filename',filename);
});
To acknowledge the message, your handler for the startRecording event needs to accept an acknowledgement callback as a parameter. You can then call that with your desired data. See Sending and getting data (acknowledgements)
socket.on('startRecording',function (socket, ackFn) {
var response;
logsRecording(function (filename) {
ackFn(filename);
});
});
Alternatively, you could add a listener for that filename event you have commented out, in the client.js:
socket.emit('startRecording');
socket.on('filename', function(filename) {
console.log('Filename received: ' + filename);
});
It might be helpful to run through Get Started: Chat application starting at the heading "Integrating Socket.IO" to get a more general understanding of Websockets.
Your server code should look like this:
socket.on('startRecording',function (callbackFn) {
var response;
logsRecording(function (filename) {
callbackFn(filename);
});
If you want to pass in data from your client:
socket.emit('startRecording', {someData: 'value'}, function (response) {
then server will be :
socket.on('startRecording',function (dataFromClient, callbackFn) {
Thank you for this helpfull hint
Here an 2020 "call" example call possible to use with moleculer microservices with four arguments:
The server responds in the callback function with two arguments err and res for the angular promise.
Angular 9 socket io
protected call(method: string, param?: any) {
try {
return new Promise((resolve, reject) => {
this.socket.emit("call", method, param, (err: any, res: unknown) => {
console.log(res);
if (err) { return reject(err); }
return resolve(res);
});
});
} catch (err) {
console.error(err);
}
}
Socket IO server response
socket.on('call', function(method, param, callbackFn){ // call method, param,
console.log(param);
switch (method) {
case "test":
console.log("test detected");
callbackFn(null , {name:"test",email:"test"});
break;
}
});

Resources