nest js websocket connection is not working with angular 11 - websocket

Created a nest js websocket and trying to connect to that from angular app version 11. Not able to connect to socket from angular 11. I am using latest version of socket.io-client.
In websocket server log says connect and disconnects.
nest js websocket file:
import { Logger } from '#nestjs/common';
import { OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit, SubscribeMessage, WebSocketGateway, WsResponse } from '#nestjs/websockets';
import { Socket,Server } from 'socket.io';
import { EventPattern } from '#nestjs/microservices';
#WebSocketGateway(3001)
export class AppGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect{
private Logger = new Logger('AppGateway');
afterInit(server: Server) {
this.Logger.log("App Gateway Initialized");
}
handleConnection(client: Socket, ...args: any[]){
this.Logger.log(`New client connected...: ${client.id}`);
client.emit('connected', 'Successfully connected to the server.');
}
handleDisconnect(client: Socket) {
this.Logger.log(`Client disconnected: ${client.id}`);
}
#SubscribeMessage('msgToServer')
handleMessage(client:Socket, text:string):WsResponse<string> {
this.Logger.log(`got new event`);
return {event: 'msgToClient', 'data': text};
}
}
angular websocket service file:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { io } from 'socket.io-client';
#Injectable({
providedIn: 'root'
})
export class WebSocketService {
socket: any;
constructor() {
this.socket = io('http://localhost:3001');
debugger;
this.socket.on('connected', function() {
console.log("connected !");
});
}
listen(eventName: string) {
return new Observable((subscriber) => {
this.socket.on(eventName, (data) => {
subscriber.next(data);
})
});
}
emit(eventName: string, data:any) {
this.socket.emit(eventName, data);
}
}
angular app.component.ts file:
import { Component, OnInit } from '#angular/core';
import { WebSocketService } from './web-socket.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit{
title = 'dashboard';
constructor(
private webSocketService: WebSocketService
) {}
ngOnInit() {
/*this.webSocketService.listen('msgToClient').subscribe((data) => {
console.log(data);
})*/
this.webSocketService.listen('connected').subscribe((data) => {
console.log(data);
})
}
}

I am using latest version of socket.io-client.
I believe you're using socketIO client v4. (If you're working with v3, the following would still be true)
Based on NestJS Websocket documentation, the NestJS socketIO server is still in v2.
#nestjs/platform-socket.io currently depends on socket.io v2.3 and socket.io v3.0 client and server are not backward compatible. However, you can still implement a custom adapter to use socket.io v3.0. Please refer to this issue for further information.
If you check the version compatibility, you will see that socketIO server v2 is not compatible with socketIO client v4.
However, socketIO server v3 is compatible with socketIO client v4. So I believe you can take a look into this issue (as mentioned in NestJS docs) and try to convert your NestJS socketIO server to support socketIO client v3. Hopefully, that would also support socketIO client v4 as well. (I didn't test this though!)
Hope this helps you. Cheers 🍻 !!!

Related

Can't get Firebase emulators to work with AngularFire 7

Good talk yesterday at the Firebase Summit about emulators! I was able to get the Functions emulator to work with AngularFire 6. I can't get the Firestore emulator or the Functions emulator to work with AngularFire 7. Here's my app.module.ts:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { initializeApp,provideFirebaseApp } from '#angular/fire/app';
import { environment } from '../environments/environment';
import { provideFirestore,getFirestore } from '#angular/fire/firestore';
import { USE_EMULATOR as USE_FIRESTORE_EMULATOR } from '#angular/fire/compat/functions';
import { USE_EMULATOR as USE_FUNCTIONS_EMULATOR } from '#angular/fire/compat/functions';
import { FormsModule } from '#angular/forms';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => getFirestore()),
],
providers: [
{ provide: USE_FIRESTORE_EMULATOR, useValue: environment.useEmulators ? ['localhost', 8080] : undefined },
{ provide: USE_FUNCTIONS_EMULATOR, useValue: environment.useEmulators ? ['localhost', 5001] : undefined }
],
bootstrap: [AppComponent]
})
export class AppModule { }
There's a smell here. I'm initializing Firebase using AngularFire 7 but I'm importing the emulator from AngularFire 6.1.0. Firebase can be initialized with AngularFire 6 or AngularFire 7 but not both, i.e., you can't mix AngularFire 6 and 7.
How do I import the emulators without using AngularFire 6?
In environments.ts I made a property useEmulators:
export const environment = {
firebase: {
projectId: 'my-awesome-project',
appId: '1:234567890:web',
storageBucket: 'my-awesome-project.appspot.com',
apiKey: 'ABCdef',
authDomain: 'my-awesome-project.firebaseapp.com',
messagingSenderId: '0987654321',
},
production: false,
useEmulators: true
};
My Cloud Function runs great in the cloud but doesn't run in the emulators.
Each time I make a change in a Cloud Function, deploy the update to the cloud, wait a minute for the deploy to propagate, test my function, and wait for the logs to show up in the Firebase Console is ten minutes. I'm looking forward to using the emulators to speed up this development cycle.
Here's the rest of my code. I doubt there's anything wrong with these files.
The Cloud Function triggers from writing a message to Firestore, changes the message to uppercase, and writes the uppercase message to a new field in the document.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.uppercaseMe = functions.firestore.document('Triggers/{docId}').onCreate((snap, context) => {
var original = snap.data().message;
functions.logger.log('Uppercasing', context.params.docId, original);
var uppercase = original.toUpperCase();
return snap.ref.set({ uppercase }, { merge: true });
});
The HTML view has a form for submitting a message. It displays the data that was written to Firestore and then displays the results from the Cloud Function.
<form (ngSubmit)="triggerMe()">
<input type="text" [(ngModel)]="message" name="message" placeholder="Message" required>
<button type="submit" value="Submit">Submit</button>
</form>
<div>{{ data$ }}</div>
<div>{{ upperca$e }}</div>
The app.component.ts controller writes the message to Firestore, reads back the message from Firestore, then sets up a document listener to wait for the cloud function to write a new field to the document.
import { Component } from '#angular/core';
import { Firestore, doc, getDoc, collection, addDoc, onSnapshot } from '#angular/fire/firestore';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
data$: any;
docSnap: any;
message: string | null = null;
upperca$e: string | null = null;
unsubMessage$: any;
constructor(public firestore: Firestore) {}
async triggerMe() {
try {
// write to Firestore
const docRef = await addDoc(collection(this.firestore, 'Triggers'), {
message: this.message,
});
this.message = null; // clear form fields
// read from Firestore
this.docSnap = await getDoc(doc(this.firestore, 'Triggers', docRef.id));
this.data$ = this.docSnap.data().message;
// document listener
this.unsubMessage$ = onSnapshot(doc(this.firestore, 'Triggers', docRef.id), (snapshot: any) => {
this.upperca$e = snapshot.data().uppercase;
});
} catch (error) {
console.error(error);
}
}
}
Firebase emulators work independently of Angular or other apps! I reread the documentation and learned that you just spin up the emulators,
firebase emulators:start
open your browser to http://localhost:4000, and you can write data in Firestore and then see the results of your function appear in Firestore. You can also read the logs. This only works with triggerable functions, not with callable functions.
Amazing what you can learn by reading the documentation. :-)

React Component subscription detect GraphQL server not responding

I have a device that sends a heartbeat to my Apollo GraphQL server every 30 seconds. I have a React component that subscribes to hbReceived, and displays the most recent heartbeat time. This works fine.
BUT,
If my GraphQL server is down, I want to handle that error. I expect these errors to be returned in the useSubscription() hook's return value error.networkError property. Instead, I just see client.ts:545 WebSocket connection to 'ws://localhost:4000/graphql' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED in the console, and the error key remains undefined in the useSubscripiton response.
schema.graphql:
type Heartbeat {
id: ID!
heartbeatTime: DateISO8601!
deviceId: ID!
}
type Subscription {
heartbeatReceived(chargePointInstallId: ID!) : Heartbeat
hbReceived(deviceId: ID!): Heartbeat
}
I made a simple version of my app in create-react-app to illustrate this problem:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Heartbeat from './Heartbeat';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
const link = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
reconnect: true,
}
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
ReactDOM.render(
<React.StrictMode>
<ApolloProvider client = {client}>
<Heartbeat deviceId={1} />
</ApolloProvider>
</React.StrictMode>,
document.getElementById('root')
);
src/Heartbeat.js
import React from 'react';
import './App.css';
import { useSubscription } from 'react-apollo';
import gql from 'graphql-tag';
export default function Heartbeat(props) {
const { loading, data, error} = useSubscription(
gql`
subscription hbReceived($deviceId: ID!) {
hbReceived(deviceId: $deviceId) {
heartbeatTime
}
}`,
{ variables:{ deviceId: `${props.deviceId}`}}
);
let mostRecentHeartbeatTimeStr;
if (error) {
console.log('Error rerturned:');
console.log(error);
mostRecentHeartbeatTimeStr = 'See console for error';
} else if (loading) {
mostRecentHeartbeatTimeStr = 'Waiting for first heartbeat';
} else {
const mostRecentHeartbeatDate = new Date(data.heartbeatReceived.heartbeatTime);
mostRecentHeartbeatTimeStr = 'Last Heartbeat: ' + mostRecentHeartbeatDate.toLocaleString('en-AU',{})
}
return (<div className='device'>
<div className='device-heading'>
Device heartbeat:
</div>
<div className='device-row'>
{mostRecentHeartbeatTimeStr}
</div>
</div>)
}
This is what I see in the console when the graphQL server is down:
WebSocket connection to 'ws://localhost:4000/graphql' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED
How do I catch that WebSocket ERR_CONNECTION_REFUSED error and display some nice message to my user?
What I have tried
I have put a connectionCallback in the options for the new WebSocketLink constructor
parameters(url,{ options: { connectionCallback(error) => { console.log(error);} });
I have tried composing a link with an onError from import { onError } from "apollo-link-error"; in it, and put { errorPolicy: 'all' } in my useSubscription call.
The documentation also says the default behaviour is that network errors are treated like GraphQL errors.
I am stuck! Any help appreciated!

WebSockets on specific route in Nest.js

I'd like to create specific API route which will be used only WebSocket (/api/events) but in all examples of implementing WebSockets on Nest.js I stumbled upon module is imported in AppModule and client is emitting events toward the root URL, which I can't do because I have this middleware;
frontend.middleware.ts
import { Request, Response } from 'express';
import { AppModule } from '../../app.module';
export function FrontendMiddleware(
req: Request,
res: Response,
next: Function,
) {
const { baseUrl } = req;
if (baseUrl.indexOf('/api') === 0) {
next();
} else {
res.sendFile('index.html', { root: AppModule.getStaticAssetsRootPath() });
}
}
Here is the EventGateway and EventModule:
event.gateway.ts
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';
#WebSocketGateway({ namespace: 'events' })
export class EventGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
findAll(client: Client, data: any): Observable<WsResponse<number>> {
return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
}
#SubscribeMessage('identity')
async identity(client: Client, data: number): Promise<number> {
return data;
}
}
event.module.ts
import { Module } from '#nestjs/common';
import { EventGateway } from './event.gateway';
#Module({
components: [EventGateway],
})
export class EventModule {}
Is there a way to create controller which will allow server-client communication via /api/events?
Yes, it is possible to create the WebsocketGateway on another path. You can just use the options of the WebsocketGateway to configure the underlying IO-Connection:
E.g:
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Client, Server } from 'socket.io';
#WebSocketGateway({ path: '/api/events', namespace: 'events' })
export class EventGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
findAll(client: Client, data: any): Observable<WsResponse<number>> {
return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item })));
}
#SubscribeMessage('identity')
async identity(client: Client, data: number): Promise<number> {
return data;
}
}
This will start the IO-Connection on http://localhost/api/events
Remember to change the connection-path also in your client. It won't be the default /socket.io path anymore, it will be /api/events in your sample.
Websocket is running on the server, not an endpoint. Therefore you cannot have it listen to requests under a specific route, rather just a port, which for Nest's default configuration happens to be the same as the HTTP one.
You could use a reverse proxy like Nginx to redirect the requests towards /api/events facing the Websocket server and also handle the redirection to index.html without changing even the Websocket server's port. Then you would not need the FrontendMiddleware class at all. It is also better since the application does not take the burden of managing request redirections.

How to catch errors with Ngxs Websocket plugin?

I am trying to build an application using Angular 6 + Ngxs + WebSocket. However, some how I am not able to connect with the WebSocket plugin provided by Ngxs.
In the documentation, Ngxs mentioned that we can catch the error using WebsocketMessageError action. (I hope I understood the documentation correctly)
Documentation Link: https://ngxs.gitbook.io/ngxs/plugins/web-socket
But I am trying to import this action in my Service class, then it says that Action is not available.
Here is the code of my WebSocketService class.
import { Injectable } from '#angular/core';
import { Store, Actions, ofActionDispatched } from '#ngxs/store';
import { WebsocketMessageError, ConnectWebSocket } from '#ngxs/websocket-plugin';
import * as Rx from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class WebSocketService {
private subject: Rx.Subject<MessageEvent>;
constructor(private store: Store, private actions$: Actions) {
this.actions$
.pipe(ofActionDispatched(WebsocketMessageError))
.subscribe(({ payload }) => {
console.log('Got action: ', payload);
});
}
public connect() {
this.store.dispatch(new ConnectWebSocket());
}
}
I am getting following error:
module #ngxs/websocket-plugin/ngxs-websocket-plugin"' has no exported member
'WebsocketMessageError'.
import WebsocketMessageError
This looks like a bug. Please report it in the github repository:
https://github.com/ngxs/store/issues

Update Angular app data in real time using websocket

I have API written in Spring on server side also managed websocket code that opens socket and continuously responds with some data (for example /getlikes returns number of likes).
How do I call this API in service that continuously checks for updated values (I don't want to use any time interval for service call)?
you can use sockjs-client and do somethjing like this.
import { Component } from '#angular/core';
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
private serverUrl = 'http://localhost:8080/socket'
private title = 'WebSockets chat';
private stompClient;
constructor(){
this.initializeWebSocketConnection();
}
public initializeWebSocketConnection(){
let ws = new SockJS(this.serverUrl);
this.stompClient = Stomp.over(ws);
let that = this;
this.stompClient.connect({}, function(frame) {
that.stompClient.subscribe("/chat", (message) => {
if(message.body) {
$(".chat").append("<div class='message'>"+message.body+"</div>")
console.log(message.body);
}
});
});
}
public sendMessage(message){
this.stompClient.send("/app/send/message" , {}, message);
$('#input').val('');
}
}
you can find a full tutorial on this in this article
#Bhagvat Lande
I think you are looking for this :
https://angular.io/guide/observables
getting live data from server continuosly and refelect changes in html in angular2

Resources