Getting event if connected in RxJs WebSocketSubject - javascript

I wonder how can i get the response, if the WebSocketSubject is connected?
In the source code (https://github.com/ReactiveX/rxjs/blob/master/src/observable/dom/WebSocketSubject.ts) they have a openObserver member of type NextObserver<Event>
socket.onopen = (e: Event) => {
const openObserver = this.openObserver;
if (openObserver) {
openObserver.next(e);
}
.....
How can i connect to it, to get the event, that the socket was opened?

The openObserver parameter accepts NextObserver interface so you can use:
let socket = new WebSocketSubject({
url: 'ws://localhost:8081',
openObserver: {
next: value => {
console.log(value);
}
}
});
Or you can create your own class that extends NextObserver and pass its instance in parameters as { ..., openObserver: new MyObserver() } .

Related

Function Binding Issues on Callbacks

I'm working with WebSocket and having an issue with a function showing incorrect data, All my code used to work with a Class-based component, I'm only trying to convert a class component that actually calls the connect method of Websocket to a functional-based component. The Websocket is class-based for instance.
So I have a WebSocket which on socket New Message sets callbacks.
socketNewMessage(data) {
console.log(data);
const parsedData = JSON.parse(data);
const command = parsedData.command;
if (command === "userChatGroups") {
this.callback[command](parsedData.chatGRoups);
}
if (command === "new_userChatGroups") {
this.callback[command](parsedData.chatGRoup);
}
}
and here are the callbacks defined -
addCallbacks(userChatGroups, newUserChatGroup) {
this.callback["userChatGroups"] = userChatGroups;
this.callback["new_userChatGroups"] = newUserChat;
}
and then websocket finally return -
const WebSocketInstance = Websocketservice.getInstance();
export default WebSocketInstance;
Now the class-based component which I'm trying to convert to functional based -
let's call this component Chats-
this calls the connect method and a Websocket instance is returned.
useEffect(() => {
if (loggedInUserDetail) {
WebSocketInstance.connect(loggedInUserDetail[0].id);
}
}, [loggedInUserDetail]);
I have a helper function which checks the status of websocket -
function waitForSocketConnection(callback) {
setTimeout(() => {
if (WebSocketInstance.state() === 1) {
console.log("connection is secure");
callback();
return;
} else {
console.log("waiting for connection");
waitForSocketConnection(callback);
}
}, 10);
}
and I check the status and map my callbacks - here is the problem-
useEffect(() => {
waitForSocketConnection(() => {
WebSocketInstance.addCallbacks(
setChatGroups,
addNewChatGroup
);
});
}, [loggedInUserDetail]);
I have a state to manage CHatGroups -
const [groups, setGroups] = useState([]);
the setChatGroups (which initially loads all the groups the users are associated with works fine and sets the state of chatsGroups) as -
const setChatGroups = useCallback((userChatGroups) => {
setGroups(userChatGroups); //whatever data it recieved from the websocket.
}, []);
but the function addNewChatGroup always shows groups value as an empty array [] (it was updated earlier with setChatGroups). If I manually check the value of groups it is an array of n length, but in addNewChatGroup function, it always shows an empty array with the initial value [].
const addNewChatGroup = useCallback(
(newCHatGroup) => {
console.log(groups); **error -> this is always empty array**
// here I have to update. add the received value with the previous
},
[groups] // callbacks used hoping this will bind
);
In the class-based component I used to set callbacks on the constructor and used to bind with this, but I'm not able to do it here, can anyone help what I'm missing?
I'm sure it is a binding issue. maybe. May I know the reason for this binding failure?
Well if I understood correctly your problem, it could be link to multiple things.
The problem actually is I don't have a clear view on all your components, maybe you can try to paste a sandbox link or something like that with a "simple structure".
I tried to reproduced a typescript version, I don't know if it could help:
class MyWebSocket {
private static _instance: MyWebSocket;
public callbacks: any = {};
public connected: boolean = false;
public socketNewMessage(data: any): void {
const parsedData = JSON.parse(data);
console.log('new message received:', parsedData);
const command = parsedData.command;
if (command === "new_userChatGroups") {
this.callbacks[command](parsedData.newGroupAdded);
}
}
public addCallbacks(elements: {command: string, func: Function}[]) {
console.log('adding callbacks...', elements);
elements.forEach(element => {
this.callbacks[element.command] = element.func;
});
}
public connect(): void {
setTimeout(() => this.connected = true, 1100);
}
public static getInstance(): MyWebSocket {
return this._instance || (this._instance = new MyWebSocket());
}
}
class SocketUtils {
static waitForSocketConnection(callback: any): void {
const waitingInterval = setInterval(() => {
if (MyWebSocket.getInstance().connected) {
console.log('socket is connected! processing callback...');
clearInterval(waitingInterval);
callback();
return;
} else {
console.log('socket is not connected after 1sec, waiting...');
}
}, 1000);
}
}
class Chat {
groups: string[] = ['group of JStw'];
new_userChatGroups(group: string) {
this.groups.push(group);
}
}
class Main {
constructor() {
const myChat = new Chat();
MyWebSocket.getInstance().connect();
// waiting connections.
SocketUtils.waitForSocketConnection(() => {
console.log('waitForSocketConnection is triggered, adding callbacks...');
// adding callbacks
MyWebSocket.getInstance().addCallbacks([{command: 'new_userChatGroups', func: myChat.new_userChatGroups.bind(myChat)}]);
});
// Waiting 5min to dispatch an message
setTimeout(() => {
// testing eventing after getting connection
MyWebSocket.getInstance().socketNewMessage(JSON.stringify({command: 'new_userChatGroups', newGroupAdded: 'group of Ranu Vijay'}));
}, 5000);
setTimeout(() => {
console.log('program finished, results of chat groups:', myChat.groups);
}, 10000);
}
}
new Main();
Output:
I'm more specialized on functional component by using react so without a link to investigate all your code, it will be complex to help.
I don't know if you are using a library, but there is this one: https://www.npmjs.com/package/react-use-websocket which seems to be really ultra simple to use without managing socket connection/disconnection.
For me if I had to implement it, I would say:
Component ChatGroup which is using websocket hook const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl); and contains the state groups, setGroups.
Component Chat which can use the sendMessage from props of ChatGroup component and call it from this component if a join a group.
Then your "parent" component is managing the state and is controlling the data.

Adding a function to a Vuex plugin?

I'm a beginner in vue and created my first application. I use vuex and I have a plugin to manage a webSocket to the server. Everything works fine as long as I only dealt with messages sent from the server to the browser.
I now would like to add a function to send messages through the socket if connected, but I'm not able to access the exported function. I'm also a beginner in Javascript programming.
Here is the plugin code:
var store = null;
var ws = null;
function startWebsocket() {
ws = new WebSocket(process.env.VUE_APP_WEBSOCKET_URL)
ws.onmessage = function (event) {
console.log("webSocket: on message: ", event.data);
store.dispatch('remoteMessage', event.data);
}
ws.onopen = function (event) {
console.log("webSocket: on open: ", event)
store.dispatch('connectionOpened');
}
ws.onclose = function (event) {
console.log("webSocket: on close: ", event)
store.dispatch('connectionClosed');
ws = null
setTimeout(startWebsocket, 5000)
}
ws.onerror = function (event) {
console.log("webSocket: on error: ", event)
}
}
export default function createWebSocketPlugin() {
return store_param => {
store = store_param;
startWebsocket();
};
}
I would like to add the following function to the plugin so that I can call it from a vuex action function.
export function sendWebSocketMsg(msg) {
if (ws) {
ws.sendMsg(msg)
}
}
In the vuex index.js file I have this:
. . .
import webSocket from '../plugins/webSocket'
. . .
export default new Vuex.Store({
. . .
actions: {
connectionOpened({ commit }) {
commit('SET_CONNECTION', true);
},
connectionClosed({ commit }) {
commit('SET_CONNECTION', false);
},
connectionError({ commit }, error) {
commit('SET_ERROR', error);
},
remoteMessage({commit}, message) {
commit('SET_MESSAGE', message);
},
pause() {
sendWebSocketMsg('{"pause":true}')
},
play() {
sendWebSocketMsg('{"pause":false}')
}
}
}
The webSocket works well and reconnects automatically.
The only thing that I'm missing is the ability to send a webSocket message.
How do I have to modify the webSocket plugin ?
I answer my question since I found the solution. It is partly given in the tutorial I followed.
I wasn't aware of it, but the plugin is a vuex plugin.
The solution is to subscribe to a vuex method. I added the empty method SEND_MESSAGE to the vuex mutations.
mutations: {
SET_ERROR(state, errStr) {
state.error = errStr;
},
SET_CONNECTION(state, status) {
state.connected = status;
},
SET_MESSAGE(state, message) {
let msg = JSON.parse(message);
. . .
},
SEND_MESSAGE() {
},
},
I also added the application specific actions:
pause({commit}) {
commit('SEND_MESSAGE', '{"pause":true}');
},
play({commit}) {
commit('SEND_MESSAGE', '{"pause":false}');
},
I call the store actions from my components like this:
methods: {
pause() {
this.$store.dispatch("pause");
},
play() {
this.$store.dispatch("play");
}
},
The only change left to do is in the plugin. I subscribe a method to call to the SEND_MESSAGE mutation. This is how it is done:
export default function createWebSocketPlugin() {
return store_param => {
store = store_param;
startWebsocket();
store.subscribe((mutation, state) => {
if (state.connected && mutation.type === 'SEND_MESSAGE' && ws) {
console.log("webSocket send "+mutation.payload);
ws.send(mutation.payload);
}
});
};
}
I added the store.subscribe instruction. We only perform the operation when the mutation is of the right type and the web socket is connected.
ws variable is local to the module it was defined, this requires to modify plugin module in order for a function to access ws, e.g.:
export function sendWebSocketMsg(msg) {
if (ws) {
ws.sendMsg(msg)
}
}
export default function createWebSocketPlugin() {...}
Then named export can be imported in module where it's used:
import webSocket, {sendWebSocketMsg} from '../plugins/webSocket'

Injected function into a class constructor throws undefined

I am learning about using typescript to build API's, I have come across two issues right now. First, I have a somewhat generic PostController Class that can accept a use-case that implements the PostMethod Interface, e.g
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
That's the interface, and the generic controller looks like this.
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
let { ...incomingHttpBody } = req.body
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
const newItem = await this.postMethod({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
And then, I can use this PostController class like this
...
const postMethod = new AddUser(UsersDb).addUser
export const postUser = new PostController(postMethod)
...
The AddUser class looks like this,
export class AddUser {
constructor(public usersDb: UserDatabase) {}
async addUser(userInfo: IUser) {
console.log({...userInfo})
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
When I do a console.log of req.body, I get the incoming body, but I keep getting TypeError: Cannot read property 'postMethod' of undefined. I am unsure how to annotate the constructor function also. I do not know what I could be doing wrong, when I console.log postUser, I do see the function passed as argument logged to the console, but when I try sending requests, it fails.
Please help, thank you.
I think there are some misconceptions here.
You're defining an interface PostMethod, which itself IS NOT a method. It is an interface. So if you try to pass an instance of this, please don't just pass a FUNCTION (or method). But pass an instance of PostMethod.
It could look like this (look at the changes I made):
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
// cloning? if so, do it like this
let incomingHttpBody = { ...req.body }
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
// change: I made a change here, you have to call postMethod.add!
const newItem = await this.postMethod.add({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
// change: instantiate AddUser which in turn implements PostMethod that can be passed...
const postMethod = new AddUser(UsersDb)
export const postUser = new PostController(postMethod)
// change: implementing PostMethod!
export class AddUser implements PostMethod {
constructor(public usersDb: UserDatabase) {}
// change: new method! this is a must, because we're implementing PostMethod!
async add(req: Request, res: Response) {
// this gets called, not addUser!
}
async addUser(userInfo: IUser) {
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
You need to instantiate the class first before you can use the method...
...
const postMethod = new AddUser(UsersDb)
//then use it
postMethod.addUser()
export const postUser = new PostController(postMethod)
...
However you could make it static so you do not need to instantiate it:
static async add(req: Request, res: Response) { ....
An function is usually written like this in an interface:
export interface PostMethod {
constructor(): void,
add(req: Request, res: Response): Promise<any>
}
I fixed the issue, after much digging and reading up I realized it was a problem with this losing its context. Hence why I was getting undefined when I do something like this.
const postMethod = new PostController(usecase)
The fix was to bind this in the constructor like so,
this.add = this.add.bind(this)
that fixed the issue as when the class was instantiated it doesn't just point to the methods of the class but also the associations of the class.
The postMethod parameter which is being passed into the constructor of PostMethod is not being stored anywhere. Store it ins a private class variable and then use the private class variable.
like -
const _postMethod = postMethod;
and then while calling call
const newItem = await this._postMethod({ source, ...incomingHttpBody })

How to call a function outside of an emitted event call function

I apologize if this is unclear, it's late and I don't know how best to explain it.
I'm using an event emitter to pass data from a server response to a function inside of a separate class in another file, but when trying to use methods in those classes, the this keyword obviously doesn't work (because in this scenario, this refers to the server event emitter) - how would I reference a function within the class itself? I've provided code to help illustrate my point a bit better
ServiceClass.js
class StreamService {
/**
*
* #param {} database
* #param {Collection<Guild>} guilds
*/
constructor (database, guilds,) {
.....
twitchListener.on('live', this.sendLiveAlert) // fire test method when we get a notification
// if there are streamers to monitor, being monitoring
winston.info('Stream service initialized')
}
..............
async get (url, params = null, headers = this.defaultHeaders) {
// check oauth token
const expirationDate = this.token.expires_in || 0
if (expirationDate <= Date.now() || !this.token) await this.getAccessToken()
// build URL
const index = 0
let paramsString = ''
for (const [key, value] of params.entries()) {
if (index === 0) {
paramsString += `?${key}=${value}`
} else {
paramsString += `&${key}=${value}`
}
}
const res = await fetch(url + paramsString, { method: 'GET', headers: headers })
if (!res.ok) {
winston.error(`Error performing GET request to ${url}`)
return null
}
return await res.json()
}
async sendLiveAlert(streamTitle, streamURL, avatar, userName, gameId, viewerCount, thumbnail, startDateTime) {
// get game name first (no headers needed)
const params = new Map()
params.set('id', gameId)
const gameData = await this.get('https://api.twitch.tv/heliix/games', params, this.defaultHeaders)
if(gameData) {
// get webhook and send message to channel
const webhookClient = new WebhookClient('755641606555697305', 'OWZvI01kUUf4AAIR9uv2z4CxRse3Ik8b0LKOluaOYKmhE33h0ypMLT0JJm3laomlZ05o')
const embed = new MessageEmbed()
.setTitle(`${userName} just went live on Twitch!`)
.setURL(streamURL)
.setThumbnail(avatar)
.addFields(
{ name: 'Now Playing', value: gameData.data[0].name },
{ name: 'Stream Title', value: streamTitle }
)
.setImage(thumbnail)
}
webhookClient.send('Webhook test', embed)
}
}
Server.js
class TwitchWebhookListener extends EventEmitter {
......................
// Routes
server
.post((req, res) => {
console.log('Incoming POST request on /webhooks')
............................
const data = req.body.data[0]
if(!this.streamerLiveStatus.get(data.user_id) && data.type === 'live') {
// pass request body to bot for processing
this.emit(
'live',
data.title, // stream title
`https://twitch.tv/${data.user_name}`, // channel link
`https://avatar.glue-bot.xyz/twitch/${data.user_name}`, // streamer avatar
data.user_name,
data.game_id,
data.viewer_count,
data.thumbnail_url,
data.started_at // do we need this?
)
}
break
default:
res.send(`Unknown webhook for ${req.params.id}`)
break
}
} else {
console.log('The Signature did not match')
res.send('Ok')
}
} else {
console.log('It didn\'t seem to be a Twitch Hook')
res.send('Ok')
}
})
}
}
const listener = new TwitchWebhookListener()
listener.listen()
module.exports = listener
Within the sendLiveAlert method, I'm trying to call the get method of the StreamService class - but because it's called directly via the emitter within server.js, this refers specifically to the Server.js class - is there any way I can use StreamService.get()? I could obviously just rewrite the code inside the method itself, but that seems unnecessary when its right there?
Change this:
twitchListener.on('live', this.sendLiveAlert)
to this:
twitchListener.on('live', this.sendLiveAlert.bind(this))
Or, you could also do this:
twitchListener.on('live', (...args) => {
this.sendLiveAlert(...args);
});
With .bind() it creates a function wrapper that resets the proper value of this for you. In the case of the arrow function, it preserves the lexical value of this for you.

How to pass data from parent to child in reactJS after the components have rendered

I've done a lot of searching and can't seem to find the answer to this - maybe I'm just not using the right terminology.
What I need to do is pass data from a WebSocket component, down to a child component. I'm already passing the WebSocket via props to the child so that it can use the send() function and send data to the socket. I need to also pass any received data via onmessage. Setting this up in the usual way inside the child doesn't work.
What I need to happen is when the data is received in the socket it gets sent to the child, with a function inside the child to then do something with it (send it via MIDI using the Web MIDI API)
Parent
class Socket extends Component {
constructor(props) {
super(props);
this.state = {
ws: null,
};
}
componentDidMount() {
this.connect();
}
connect = () => {
var ws = new WebSocket("ws://127.0.0.1:8000/ws/");
ws.onopen = () => {
this.setState({ ws: ws });
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
var midi = data["command"]; // Need to send this to the child somehow.
};
......
}
render() {
return <MIDIComponent websocket={this.state.ws} />;
}
}
EDIT: So I've managed to get the data from the parent to the child, and I've rendered it to the screen for testing. But I can't extract it inside the functions I need. I've tried combinations of using arrow functions, binding 'this' etc. I either can't access this or the midi ports either come back as undefined or null, the default value.
Child
class MIDIComponent extends Component {
constructor(props) {
super(props);
this.state = {
midiInput: null,
midiOutput: null,
};
}
componentDidMount() {
const that = this;
this.setupMIDI(that);
}
setupMIDI = (that) => {
navigator.requestMIDIAccess({ sysex: true }).then(onMIDISuccess);
function onMIDISuccess(midiAccess) {
that.setState({ midiInput: Array.from(midiAccess.inputs.values())[0] });
that.setState({ midiOutput: Array.from(midiAccess.outputs.values())[1] });
that.state.midiInput.onmidimessage = getMIDIMessage;
// storing the midi ports in the state like this, but it doesnt work.
}
function getMIDIMessage(msg) {
console.log(msg.data);
that.props.websocket.send(
JSON.stringify({ message: Array.from(msg.data), type: "config" })
);
}
};
sendMIDIMessage = (msg) => {
this.state.midiOutput.send(msg); // need to get to the midiOutput port here to send the data
};
render() {
return <div key={this.props.midi}>{this.props.midi}</div>; // Just using this to render the data to the screen for testing
}
}
I should probably mention that I will be eventually having two Child Components that will need to receive data from the Socket depending on the type of data received. At the moment I'd just like to get it set up using one. Any help would be greatly appreciated.
Simply save the received data in the state as well like this:
class Socket extends Component {
constructor(props) {
super(props);
this.state = {
ws: null,
midi: [] // Create an empty array so that the child always received something and not undefined
};
}
componentDidMount() {
this.connect();
}
connect = () => {
var ws = new WebSocket("ws://127.0.0.1:8000/ws/");
ws.onopen = () => {
this.setState({ ws: ws });
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
const midi = data["command"]; // Need to send this to the child somehow.
this.setState({
midi // Save the received data in the state
});
};
}
render() {
const {ws, midi} = this.state; // Extract the data from the state
return <MIDIComponent websocket={ws} midi={midi}/>; // Pass the data as a prop to the child
}
}

Categories