rsocketjs channel not established with spring-boot rsocket server - reactjs

I have a simple rsocket backend with one message mapping defined :
#Controller
class MessageController {
#MessageMapping("test")
public Flux<String> test(Flux<String> stringFlux) {
return stringFlux.map(String::toUpperCase);
}
}
On the frontend side I try to connect to this channel from typescript + React + rsocketjs application.
Rsocket client :
class ChatServerClient {
private readonly host: String;
private readonly port: number;
private rsocket!: ReactiveSocket<any, any>;
private constructor(host: String = "localhost", port: number = 9090) {
this.host = host;
this.port = port
}
private async createClient() {
const client = new RSocketClient(
{
setup: {
dataMimeType: 'application/json',
keepAlive: 1000000, // avoid sending during test
lifetime: 100000,
metadataMimeType: MESSAGE_RSOCKET_COMPOSITE_METADATA.string,
},
transport: new RSocketWebsocketClient({
debug: true,
url: "ws://" + this.host + ":" + this.port,
wsCreator: (url) => {
return new WebSocket(url);
}
}, BufferEncoders),
errorHandler: (e) => {
console.log(e)
}
}
);
return await client.connect();
}
public static CreateAsync = async (host: String = "localhost", port: number = 9090) => {
const client = new ChatServerClient(host, port);
client.rsocket = await client.createClient()
return client;
};
test(pushMessage : Flowable<String>) {
const metadata = encodeCompositeMetadata(
[
[MESSAGE_RSOCKET_ROUTING.string, encodeRoute("test")]
]
)
const payloads = pushMessage.map(m => {return {
data : m,
metadata: metadata
}})
return this.rsocket.requestChannel(payloads)
}
}
export { ChatServerClient };
Simple component estabilishing connection :
interface ChatWindowProps { }
const ChatWindow: FC<ChatWindowProps> = (props: ChatWindowProps) => {
const [rsocket, setRsocket] = useState<ChatServerClient | null>(null)
useEffect(() => {
async function getClient() {
const client = await ChatServerClient.CreateAsync("localhost", 9090)
setRsocket(client)
}
getClient()
}, [setRsocket])
console.log("rerender")
const just : Flowable<String> = Flowable.just("a", "b")
const recivedStrings = rsocket?.test(just)
console.log("received strings " + recivedStrings)
recivedStrings?.subscribe({
onNext: m => console.log(m),
onSubscribe: s => {console.log("received strings on subscribe"); s.request(3)}
})
return (
<div className={styles.ChatWindow} data-testid="ChatWindow">
{ rsocket ? (
<div className="chat-container">
Connected
</div>
) : (
<div>Not Connected</div>
)}
</div>
)
};
export default ChatWindow;
I expect that on component rerender, when rsocket client is connected, and I establish the channel communication I would receive "A" and "B" in console but that is not the case, it seems that the channel is not created but client is connected to the server.
Other remarks :
this is just a simplification - other endpoints which use request-response style work so I do not think this is the problem with server setup
I can even see that the returned Flowable is subscribed to but nothing happens on the frontend side.
also on the server side I cannot see any frames which could indicate that any channel connection was established.
The question is - why is the channel not created and items are not emmited to the server and what is the correct way to use channel style communication from typescript rsocketjs client?

Related

how to keep a SignalR connection alive in react native application even when the device is in sleep mode

I have a signalR connection with specific configuration.I write a provider in order to create a connection instance and serve that connection to all components in my app (using react context). Creating a connection instance is done inside a class and this connection instance is somehow singelton.
then by using a package called
react-native-background-timer
I follow the document and create a util function in order to run starting connection task in background.
here is all the staff i've done :
signalR Class:
class SignalR {
private static instance: SignalR;
connection: HubConnection | undefined;
private appGuid: string;
private mobileNumber: string;
private countryCode: string;
private constructor(
appGuid: string,
mobileNumber: string,
countryCode: string,
) {
this.appGuid = appGuid;
this.mobileNumber = mobileNumber;
this.countryCode = countryCode;
this.connection = new HubConnectionBuilder()
.withUrl(`${config.api.url}/hub/notificationhub`, {
headers: {
AppGuid: this.appGuid,
AppPhoneNumber: this.mobileNumber,
AppCountryCode: this.countryCode,
},
timeout: 60000,
logMessageContent: true,
transport: 1,
skipNegotiation: true,
})
.build();
}
public static getInstance(appGuid: string, mobileNumber: string): SignalR {
if (!SignalR.instance) {
SignalR.instance = new SignalR(appGuid, mobileNumber);
}
if (this.instance.connection?.state === 'Disconnected') {
SignalR.instance.start();
}
return SignalR.instance;
}
start() {
console.log('this is start');
this.connection
?.start()
.then(res => {
console.log('SignalR connection started successfuly');
})
.catch(err => {
console.log('SignalR connection not started: ', err);
});
}
stop() {
this.connection?.stop();
}
}
export default SignalR;
this is Provider :
export const SignalRProvider = ({children, otpObject}: any) => {
const {appGuid, mobileNumber,countryCode } = otpObject;
const signalr = SignalR.getInstance(appGuid, mobileNumber,countryCode );
const restartConnection = () => {
if (signalr.connection?.state === 'Disconnected') {
signalr.start();
}
};
useEffect(() => {
const intervalId = backgroundTask(restartConnection);
}, []);
return (
<SignalRContext.Provider value={{connection: signalr.connection}}>
{children}
</SignalRContext.Provider>
);
};
and this is utils which takes a function and run it in background:
import BackgroundTimer from 'react-native-background-timer';
const backgroundTask = (someTask: any) => {
const intervalId = BackgroundTimer.setInterval(() => {
someTask();
}, 30000);
return intervalId;
};
export default backgroundTask;
All I want is to keep this background task running when device goes in sleep mode,
if I could do this it means that I have stable connection.
Can anyone help me?
thanks in advance.

Cannot send message after handshake in SockJsClient (React)

I am using Spring Boot-React JS and trying to send message through WebSocket. Handshake is fine, i can subscribe with no problem. When i try to send data to a topic, does not work. onMessage did not get triggered.
WebSocketConfig
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// prefix for server to client message destination
registry.enableSimpleBroker("/ws-push-notification");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// SockJS connection endpoint
registry.addEndpoint("/ws-push-notifications").setAllowedOrigins(allowedOrigin).withSockJS();
}
WebSocketDispatcher
public void pushNotification(String pushToken, PushNotificationDto notificationDto) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(pushToken);
headerAccessor.setLeaveMutable(true);
template.convertAndSendToUser(pushToken, "/ws-push-notification/item/" + pushToken,
notificationDto, headerAccessor.getMessageHeaders());
}
I checked data and token, it is matching.
React part:
const Client = ({
props,
autoReconnect = true
}) => {
const url = "http://localhost:8080/ws-push-notifications?access_token=" + localStorage.getItem(STORAGE_KEY_AT);
const topic = "ws-push-notification/item/" + props.pushToken;
const headers = {
"Content-Type": CONTENT_TYPE_JSON,
Authorization: "Bearer " + localStorage.getItem(STORAGE_KEY_AT)
};
const onConnect = () => {
props.changeSocketConnectionStatus(true);
console.info(`Subscribed to socket ${SUDate.toISOLocalDateTime(new Date())}`);
};
const onMessage = (data) => {
console.log("This part not getting triggered!!!")
};
return (
<SockJsClient
url={url}
topics={[topic]}
headers={headers}
subscribeHeaders={headers}
onConnect={onConnect}
onMessage={onMessage}
onDisconnect={onDisconnect}
onConnectFailure={onConnectFailure}
autoReconnect={autoReconnect}
/>
);
};
export default Client;
template.convertAndSendToUser(pushToken, "/ws-push-notification/item/" + pushToken,
notificationDto, headerAccessor.getMessageHeaders());
this send the dto to:
/ws-push-notification/item/771063a4-fcea-454f-9673-dedc842290bb905557640
and this part is the same here.
const topic = "ws-push-notification/item/" + props.pushToken;
Why is not working?
Problem was not receiving the data. It was sending it.
template.convertAndSend("/ws-push-notification/item/" + pushToken, notificationDto);
solved my problem.

ReactJS: STOMP subscription to multiple topics

My React code creates a WebSocket connection to our company's ActiveMQ 5.15.5 server, and then subscribes to the following two topics: salary and decoding. The problem is that the code is only able to subscribe to one of the topics. It cannot subscribe to both.
const client = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const headers = { id: 'username' };
client.debug = null;
client.connect('user', 'pass', () => {
client.subscribe(
'/topic/salary', //BREAKPOINT was set here
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
headers,
);
client.subscribe(
'/topic/decoding', //BREAKPOINT was set here
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
headers,
);
});
So in the code above I put a break-point at client.subscribe('/topic/decoding... and client.subscribe('/topic/salary.... I saw that it only subscribes to /topic/decoding but not /topic/salary.
Does anyone know how I can fix this issue so that it subscribes to both topics?
From Stomp documentation:
Since a single connection can have multiple open subscriptions with a server, an id header MUST be included in the frame to uniquely identify the subscription. The id header allows the client and server to relate subsequent MESSAGE or UNSUBSCRIBE frames to the original subscription.
Within the same connection, different subscriptions MUST use different subscription identifiers.
Stomp API definition:
subscribe(destination, callback, headers = {})
So for my understanding, You can't have the same username id for both of your subscriptions
Try creating 2 clients, e.g.:
const salaryClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const salaryHeaders = { id: 'salary' };
salaryClient.debug = null;
salaryClient.connect('user', 'pass', () => {
salaryClient.subscribe(
'/topic/salary',
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
salaryHeaders,
);
});
const decodingClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj7.stomp');
const decodingHeaders = { id: 'decoding' };
decodingClient.debug = null;
decodingClient.connect('user', 'pass', () => {
decodingClient.subscribe(
'/topic/decoding',
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
decodingHeaders,
);
});

React/Socket.io - Client emits to all other clients only once and then only emits to itself after that

Running into an issue with React/Socket.io. I have two different socket emitters/listeners: one for a chat, and one for keeping track live changes to the application. I have two separate windows running localhost. The issue is when i emit a change on one window, the other window can receive that change the first time but never again (i.e. get first chat message but none that follow). After that first emit/receive, the sending client starts to receive its own emitters.
front end code:
`
socket = io("localhost:3002");
componentDidMount() {
//get id from url
const { id } = this.props.match.params;
//join specific room for project
this.socket.on("connect", () => {
this.socket.emit("room", this.projectId);
});
//listener for incoming messages
this.socket.on("RECEIVE_MESSAGE", (data) => {
this.props.addChat(this.projectId, data);
});
this.socket.on("UPDATE_PROJECT", () => {
console.log("update");
this.props.fetchProject(id);
});
}
emitTaskChange = () => {
this.socket.emit("TASK_CHANGE", { data: null });
};
onChatSubmit = (e) => {
e.preventDefault();
//create object with current user as author, message, and a timestamp
const chat = {
author: this.props.currentUser.name,
message: this.state.newChat,
createdAt: new Date().toLocaleString(),
};
//send message through socket
this.socket.emit("SEND_MESSAGE", chat);
//call action creator to add new chat
this.props.addChat(this.projectId, chat);
this.setState({ currentMessage: "" });
};
handleTaskEdit = (taskId, currentStatus) => {
const newStatus = currentStatus === "todo" ? "inprogress" : "completed";
this.props.editTask(this.projectId, taskId, newStatus);
this.emitTaskChange();
};
`
backend code:
`
const io = socket(server);
//create separate chat rooms using project id
io.on("connection", (socket) => {
socket.on("room", (room) => {
socket.join(room);
socket.in(room).on("SEND_MESSAGE", (message) => {
socket.emit("RECEIVE_MESSAGE", message);
});
socket.in(room).on("TASK_CHANGE", (data) => {
socket.emit("UPDATE_PROJECT", data);
});
});
`
found the error:
had to change the server-side code from socket.on and instead use the io object that was initialized such as io.sockets.on

How to change DB connections on the fly with GraphQL?

We have a GraphQL server setup which uses the exact same set of GraphQL calls on different client systems. These clients have the same exact tables/structures which do not change.
I'd like to make a GraphQL calls which indicate which DB connection to use. OR if there is a similar alternative with GraphQL, I'm all ears.
I've been Googling, forum diving, and document scanning for a while...
One possible solution is to create a directives.
const typeDefs = gql`
directive #db(client: DbClient) on FIELD_DEFINITION
type DbClient {
DB_CLIENT_1
DB_CLIENT_2
}
type Query {
users: [User] #db
}
`;
// Directive
// db client connection config
const dbConnectionConfig = {
DEFAULT : {...},
DB_CLIENT_1: {...},
DB_CLIENT_2: {...}
}
class DbClientDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
const { client } = this.args;
field.args.push({
name: 'client',
type: GraphQLString
});
field.resolve = async function (
source,
{ client, ...otherArgs },
context,
info,
) {
let connectionConfig = dbConnectionConfig.DEFAULT;
if(Object.prototype.hasOwnProperty.call(dbConnectionConfig, client)) {
connectionConfig = dbConnectionConfig[client];
}
/**
create a DB client with the given config. Even you can persist the DB connection is a hashMap such that you don't need to establish a new connection every time.
**/
const dbSession = new DbClient(connectionConfig);
// put the dbSession in a context such that it can be used by the resolver.
context.dbSession = dbSession;
return resolve.call(this, source, otherArgs, context, info);
};
field.type = GraphQLString;
}
}
// UserResolver.js
const UserResolver = {
Query: {
users(async (parent, args, context, info) => {
const dbSession = context.dbSession;
// use the dbSession to fetch the results. Note, here resolve need not to know about the db client details.
return dbSession.findAll();
}
}
const resolvers = merge(
UserResolver
);
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
date: DbClientDirective
}
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
You can now pass client argument to a user query to indicate which DB connection to use.
Example 1 - this will use DEFAULT as DB client
query {
users {
name
userId
}
}
Example 2 - this will use DB_CLIENT_1 as DB client
query {
users(client: DB_CLIENT_1) {
name
userId
}
}

Resources