How to reuse Promise<void> after using it once in my Angular service? - promise

I have a problem with my Angular Promise<void> in one of my services.
The service establishes SignalR connection with the server's hub. And I am waiting with broadcasting to the hub for connection to be established:
export class SignalRService {
private hubConnection: signalR.HubConnection;
private url = environment.apiUrl + 'messageHub';
private thenable: Promise<void>;
public startConnection = (): void => {
if (!this.hubConnection) {
const token = this.auth.getAuth().token;
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.url, { accessTokenFactory: () => token })
.build();
this.startHubConnection();
this.hubConnection.onclose(() => this.connectionHubClosed());
}
};
private startHubConnection() {
this.thenable = this.hubConnection.start();
this.thenable
.then(() => console.log('Connection started!'))
.catch((err) => console.log('Error while establishing connection :('));
}
public broadcastGameState = (): void => {
this.thenable.then(() => {
const name = this.auth.getAuth().displayName;
let message = this.game.getGame();
this.hubConnection
.invoke('SendMessage', message)
.catch((err) => console.error(err));
});
};
But after I will close my connection with:
public stopConnection = (): void => {
if (this.hubConnection) {
this.hubConnection.stop();
}
};
and use again my service, I can not use my thenable anymore, and broadcastGameState does not wait anymore for it, throwing an error:
Error: Cannot send data if the connection is not in the 'Connected'
State.
I do not understand why?

xxh you got your point and made me thinking, so I found the problem :) After calling:
public stopConnection = (): void => {
if (this.hubConnection) {
this.hubConnection.stop();
}
};
on connection close is called:
private connectionHubClosed(): void {
//trigerred when lost connection with server
alert('todo: SignalR connection closed');
}
BUT this.hubConnection is still with assigned value, so because of if (!this.hubConnection) new this.thenable was skipped.

Related

websocket_sockJS : all transport error. after adding { transports: ['websocket']}

background:
whenever I open WebSocket page
I had a few XHR_SEND? 404 - error but finally XHR_SEND? got success response and connected to WebSocket.
So to avoid this 404 error, I decide to use WebSocket only. so I added this
: return new SockJS(connectionUrl,, null, { transports: ['websocket']});
then now..
XHR_SEND? are gone but it doesn't connect to server at all.
+FYI: I have 2 servers ..(i think because of this previously I got XHR_send error. )
The below screenshot is repeating. but never connected
JAVA
#Configuration
#EnableWebSocketMessageBroker
public class BatchSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/batch-socket");
registry.addEndpoint("/batch-socket").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/socketio/");
}
ANGULAR7
import { Injectable, OnDestroy, Inject, Optional } from '#angular/core';
import * as SockJS from '../../../assets/lib/sockjs.min.js';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter, first, switchMap } from 'rxjs/operators';
import { StompSubscription, Stomp, Client, Frame, Message, StompConfig, Versions } from '#stomp/stompjs';
#Injectable({
providedIn: 'root'
})
export class SocketService {
private client: Client;
private state: BehaviorSubject<any>;
private baseUrl: any = "/" + window.location.href.substr(0).split('/')[3] + "/";
constructor() {
}
init() {
let connectionUrl = this.baseUrl + "batch-socket";
console.log("MY URL is " + connectionUrl);
return new Promise((resolve, reject) => {
let config = new StompConfig();
config.heartbeatOutgoing = 10000;
config.heartbeatIncoming = 10000;
config.stompVersions = new Versions(['1.0', '1.1']);
config.webSocketFactory = function () {
return new SockJS(connectionUrl, null, { transports: ['websocket']});
//PREVIOUS : return new SockJS(connectionUrl)
}
config.debug = function (str) {
console.log("#socketDebug: " + str)
}
this.client = new Client();
this.client.configure(config);
console.log(this.client);
console.log("#socketSvc: starting connection...");
const _this = this;
this.client.onConnect = function (frame) {
console.log("#socketSvc: connection established.");
console.log(frame);
_this.state = new BehaviorSubject<any>(SocketClientState.ATTEMPTING);
_this.state.next(SocketClientState.CONNECTED);
resolve(frame.headers['user-name']);
}
this.client.onWebSocketClose = function (msg){
console.log("#socketSvc: connection closed.");
console.log(msg);
}
this.client.onWebSocketError = function(msg){
console.log("#socketSvc: connection error.");
console.log(msg);
}
this.client.onDisconnect = function(msg){
console.log("#socketSvc: socket disconnected.");
console.log(msg);
//this.init();
}
this.client.onStompError = function(msg){
console.log("#socketSvc: stomp error occurred.");
console.log(msg);
}
this.client.activate();
});
}
private connect(): Observable<Client> {
return new Observable<Client>(observer => {
this.state.pipe(filter(state => state === SocketClientState.CONNECTED)).subscribe(() => {
observer.next(this.client);
});
});
}
onPlainMessageReceived(topic: string): Observable<string> {
return this.onMessageReceived(topic, SocketService.textHandler);
}
onMessageReceived(topic: string, handler = SocketService.jsonHandler): Observable<any> {
return this.connect().pipe(first(), switchMap(client => {
return new Observable<any>(observer => {
const subscription: StompSubscription = client.subscribe(topic, message => {
observer.next(handler(message));
});
return () => client.unsubscribe(subscription.id);
});
}));
}
static jsonHandler(message: Message): any {
return JSON.parse(message.body);
}
static textHandler(message: Message): string {
return message.body;
}
disconnect() {
this.connect().pipe(first()).subscribe(client => client.deactivate());
this.client.deactivate();
}
}
export enum SocketClientState {
ATTEMPTING, CONNECTED
}
I found a reason for this issue.
I realized that I have 2 war files.
hence one has my code(socket connection) , the other one doesnt have a code (socket connection).
so it throws the error.
=> resolved by removing the war file that doesn't have socket connection.

reconnect web socket if it is closed

My web socket connection code :
public connect(): Subject<MessageEvent> {
if (!this.subject) {
this.subject = this.create(this.url);
}
this.ws.onerror = () => {
this.close();
let refresh = setInterval(() => {
this.subject = null;
this.connect();
this.ws.onopen = () => {
clearInterval(refresh)
}
}, 5000);
}
return this.subject;
}
private create(url: string){
this.ws = new WebSocket(url);
const observable = Observable.create((obs: Subject<MessageEvent>) => {
this.ws.onmessage = obs.next.bind(obs);
this.ws.onerror = obs.error.bind(obs);
this.ws.onclose = obs.complete.bind(obs);
this.ws.onclose = function () {
console.log("trying to reconnect");
this.connect();
}
return this.ws.close.bind(this.ws);
});
const observer = {
next: (data: any) => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
};
return Subject.create(observer, observable);
}
I want to reconnect web socket if connection closes. At the moment the function gets truggered when i stop the web socket. BUt is not connecting again .I See error "this.connect is not a function" .How to work with angular recursive functions?
Don't use function keyword to create your callback when using this inside of it if you aren't aware of how it changes the this reference depending on the execution context, use arrow function instead
To make it reconnect, change this
this.ws.onclose = function () {
console.log("trying to reconnect");
this.connect();
}
To this
this.ws.onclose = () => {
console.log("trying to reconnect");
this.subject = null;
this.connect();
}

Spring Boot sockjs + stomp, cannot get a connection

I'm trying to get a sockjs + stomp connection to my spring boot websockets. This is my configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
private final String MESSAGE_BROKER_PREFIX = "/topic";
private final String WEBSOCKET_PREFIX = "/sockjs-node";
private final String REQUEST_PREFIX = "/";
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint(WEBSOCKET_PREFIX)
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker(MESSAGE_BROKER_PREFIX);
config.setApplicationDestinationPrefixes(REQUEST_PREFIX);
}
}
And and my endpoint definition:
#Controller
public class Foo {
#SubscribeMapping("/{pipelineId}/{topic}")
private void subscribe(
HttpSession session,
#PathVariable String pipelineId,
#PathVariable String topic
) {
System.out.println(session.getId());
}
#EventListener
public void onApplicationEvent(SessionConnectEvent event) {
System.out.println(event.getSource());
}
#EventListener
public void onApplicationEvent(SessionDisconnectEvent event) {
System.out.println(event.getSessionId());
}
}
And from the javascript side:
var ws = new SockJS('/sockjs-node');
var client = Stomp.over(ws);
var subscription = client.subscribe("/topic/foo/bar", () => {
console.log("asdas");
});
but the connection does not happen and none of the methods get invoked. In the javascript console I can see:
>>> SUBSCRIBE
id:sub-0
destination:/topic/lala
stomp.js:199 Uncaught TypeError: Cannot read property 'send' of undefined
at Client._transmit (webpack:///./node_modules/#stomp/stompjs/lib/stomp.js?:199:26)
at Client.subscribe (webpack:///./node_modules/#stomp/stompjs/lib/stomp.js?:468:12)
at Object.eval (webpack:///./src/index.js?:128:27)
I am able to connect using wscat --connect ws://localhost:8080/sockjs-node/902/phebsu4o/websocket, but interestingly enough only the disconnect handler gets invoked and the connect handler doesn't. What am I missing here?
I found a js client which actually works on github.
import React from "react";
import SockJS from "sockjs-client";
import Stomp from "stompjs";
import PropTypes from "prop-types";
class SockJsClient extends React.Component {
static defaultProps = {
onConnect: () => {},
onDisconnect: () => {},
getRetryInterval: (count) => {return 1000 * count;},
headers: {},
autoReconnect: true,
debug: false
}
static propTypes = {
url: PropTypes.string.isRequired,
topics: PropTypes.array.isRequired,
onConnect: PropTypes.func,
onDisconnect: PropTypes.func,
getRetryInterval: PropTypes.func,
onMessage: PropTypes.func.isRequired,
headers: PropTypes.object,
autoReconnect: PropTypes.bool,
debug: PropTypes.bool
}
constructor(props) {
super(props);
this.state = {
connected: false
};
this.subscriptions = new Map();
this.retryCount = 0;
}
componentDidMount() {
this.connect();
}
componentWillUnmount() {
this.disconnect();
}
render() {
return (<div></div>);
}
_initStompClient = () => {
// Websocket held by stompjs can be opened only once
this.client = Stomp.over(new SockJS(this.props.url));
if (!this.props.debug) {
this.client.debug = () => {};
}
}
_cleanUp = () => {
this.setState({ connected: false });
this.retryCount = 0;
this.subscriptions.clear();
}
_log = (msg) => {
if (this.props.debug) {
console.log(msg);
}
}
connect = () => {
this._initStompClient();
this.client.connect(this.props.headers, () => {
this.setState({ connected: true });
this.props.topics.forEach((topic) => {
this.subscribe(topic);
});
this.props.onConnect();
}, (error) => {
if (this.state.connected) {
this._cleanUp();
// onDisconnect should be called only once per connect
this.props.onDisconnect();
}
if (this.props.autoReconnect) {
this._timeoutId = setTimeout(this.connect, this.props.getRetryInterval(this.retryCount++));
}
});
}
disconnect = () => {
// On calling disconnect explicitly no effort will be made to reconnect
// Clear timeoutId in case the component is trying to reconnect
if (this._timeoutId) {
clearTimeout(this._timeoutId);
}
if (this.state.connected) {
this.subscriptions.forEach((subid, topic) => {
this.unsubscribe(topic);
});
this.client.disconnect(() => {
this._cleanUp();
this.props.onDisconnect();
this._log("Stomp client is successfully disconnected!");
});
}
}
subscribe = (topic) => {
let sub = this.client.subscribe(topic, (msg) => {
this.props.onMessage(JSON.parse(msg.body));
});
this.subscriptions.set(topic, sub);
}
unsubscribe = (topic) => {
let sub = this.subscriptions.get(topic);
sub.unsubscribe();
this.subscriptions.delete(topic);
}
// Below methods can be accessed by ref attribute from the parent component
sendMessage = (topic, msg, opt_headers = {}) => {
if (this.state.connected) {
this.client.send(topic, opt_headers, msg);
} else {
console.error("Send error: SockJsClient is disconnected");
}
}
}
export default SockJsClient;

Getting Messages In Skipped Queue

Can someone help me understand why I'm getting response messages (CreditAuthorizationResponse) in my skipped queue (mtSubscriber_creditAuthRequest_queue_skipped)? The sender is receiving the responses as expected, but they are also going to the skipped queue.
I've created the following consumer, which is working as expected except for the messages going into the skipped queue:
class CreditAuthorizationConsumer : IConsumer<CreditAuthorizationRequest>
{
private Func<string, Task> _outputDelegate2;
public CreditAuthorizationConsumer(Func<string, Task> outputDelegate)
{
_outputDelegate2 = outputDelegate;
}
public async Task Consume(ConsumeContext<CreditAuthorizationRequest> context)
{
await _outputDelegate2($"Received: {context.Message}: {context.Message.CardNumber}");
await context.RespondAsync<CreditAuthorizationResponse>(new CreditAuthorizationResponse(true));
await _outputDelegate2($"Sent CreditAuthorizationResponse for card request {context.Message.CardNumber}");
}
}
Here is where I'm sending the request:
private async Task SendCreditAuthRequestAsync(int numberToSend)
{
for (int i = 0; i < numberToSend; i++)
{
var cardNumber = generateCardNumber();
await SendRequestAsync(new CreditAuthorizationRequest(cardNumber), "mtSubscriber_creditAuthRequest_queue");
await WriteOutputAsync($"Sent credit auth request for card {cardNumber}.");
}
}
Here is where I'm initializing my client-side bus:
private void InitializeBus()
{
_messageBus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri(hostUriTextBox.Text), h =>
{
h.Username("guest");
h.Password("guest");
});
sbc.ReceiveEndpoint(host, "mtSubscriber_creditAuthResponse_queue", endpoint =>
{
endpoint.Handler<CreditAuthorizationResponse>(async context =>
{
await WriteOutputAsync($"Received: {context.Message}: {context.Message.IsAuthorized}");
});
});
});
}
Here is where I'm initializing my service-side bus:
private void InitializeBus()
{
_messageBus = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri(hostUriTextBox.Text), h =>
{
h.Username("guest");
h.Password("guest");
});
sbc.ReceiveEndpoint(host, "mtSubscriber_creditAuthRequest_queue", endpoint =>
{
endpoint.Consumer(() => new CreditAuthorizationConsumer(WriteOutputAsync));
});
}
}
Alexey Zimarev was right -- my responses were bound to my request queue (in addition to the response queue). Simply deleting that binding resolved the problem and it hasn't come back. thanks!

Angular2/Websocket: how to return an observable for incoming websocket messages

I'm going to use Angular2 to receive websocket incoming messages and update a webpage based on those received messages. Right now, I'm using a dummy echo websocket service and will replace it.
From my understanding, the function which receive websocket messages has to return an observable that is subscribed by a handler who will update the webpage. But I can't figure out how to return an observable.
Code snippet is attached below. The MonitorService creates a websocket connection and return an observable containing the received messages.
#Injectable()
export class MonitorService {
private actionUrl: string;
private headers: Headers;
private websocket: any;
private receivedMsg: any;
constructor(private http: Http, private configuration: AppConfiguration) {
this.actionUrl = configuration.BaseUrl + 'monitor/';
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.headers.append('Accept', 'application/json');
}
public GetInstanceStatus = (): Observable<Response> => {
this.websocket = new WebSocket("ws://echo.websocket.org/"); //dummy echo websocket service
this.websocket.onopen = (evt) => {
this.websocket.send("Hello World");
};
this.websocket.onmessage = (evt) => {
this.receivedMsg = evt;
};
return new Observable(this.receivedMsg).share();
}
}
Below is another component which subscribes to the observable returned from above and updates webpages correspondingly.
export class InstanceListComponent {
private instanceStatus: boolean
private instanceName: string
private instanceIcon: string
constructor(private monitor: MonitorService) {
this.monitor.GetInstanceStatus().subscribe((result) => {
this.setInstanceProperties(result);
});
}
setInstanceProperties(res:any) {
this.instanceName = res.Instance.toUpperCase();
this.instanceStatus = res.Status;
if (res.Status == true)
{
this.instanceIcon = "images/icon/healthy.svg#Layer_1";
} else {
this.instanceIcon = "images/icon/cancel.svg#cancel";
}
}
}
Now, I'm running into this error in the browser console
TypeError: this._subscribe is not a function
I put it on a plunker and I added a function for sending message to the Websocket endpoint. Here is the important edit:
public GetInstanceStatus(): Observable<any>{
this.websocket = new WebSocket("ws://echo.websocket.org/"); //dummy echo websocket service
this.websocket.onopen = (evt) => {
this.websocket.send("Hello World");
};
return Observable.create(observer=>{
this.websocket.onmessage = (evt) => {
observer.next(evt);
};
})
.share();
}
Update
As you mentioned in your comment, a better alternative way is to use Observable.fromEvent()
websocket = new WebSocket("ws://echo.websocket.org/");
public GetInstanceStatus(): Observable<Event>{
return Observable.fromEvent(this.websocket,'message');
}
plunker example for Observable.fromEvent();
Also, you can do it using WebSocketSubject, although, it doesn't look like it's ready yet (as of rc.4):
constructor(){
this.websocket = WebSocketSubject.create("ws://echo.websocket.org/");
}
public sendMessage(text:string){
let msg = {msg:text};
this.websocket.next(JSON.stringify(msg));
}
plunker example
Get onMessage data from socket.
import { Injectable } from '#angular/core';
import {Observable} from 'rxjs/Rx';
#Injectable()
export class HpmaDashboardService {
private socketUrl: any = 'ws://127.0.0.0/util/test/dataserver/ws';
private websocket: any;
public GetAllInstanceStatus(objStr): Observable<any> {
this.websocket = new WebSocket(this.socketUrl);
this.websocket.onopen = (evt) => {
this.websocket.send(JSON.stringify(objStr));
};
return Observable.create(observer => {
this.websocket.onmessage = (evt) => {
observer.next(evt);
};
}).map(res => res.data).share();
}
**Get only single mesage from socket.**
public GetSingleInstanceStatus(objStr): Observable<any> {
this.websocket = new WebSocket(this.socketUrl);
this.websocket.onopen = (evt) => {
this.websocket.send(JSON.stringify(objStr));
};
return Observable.create(observer => {
this.websocket.onmessage = (evt) => {
observer.next(evt);
this.websocket.close();
};
}).map(res => res.data).share();
}
}
A different approach I used is with subject:
export class WebSocketClient {
private client: WebSocket | undefined;
private subject = new Subject<string>();
...
private connect() {
const client = new WebSocket(fakeUrl);
const client.onmessage = (event) => {
this.subject.next(event.data);
};
}
private watch() { return this.subject } // can be mapped
}
And using it will be in my opinion clearer:
const client = new WebSocketClient(); // can also be injected
client.connect();
client.watch().subscribe(x => ...);
Happy coding!

Resources