i am using of this code for receive MSG to socket in angular 2 but i have used in global application but it's created multiple time msgs.. on routeing another pages i created one chat-box component which opened global after open like Facebook chat-box.
`get-messages() {
let observable = new Observable(observer => {
this.socket = io(this.url);
this.socket.on('message', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
})
return observable;
} `
I am not sure if this will help or not. In angular 1.x I use
$scope.$on('$destroy', function(event) {
// Code to un observe the socket here...
});
I am sure there is an equivalent in angular 2
Related
I'm trying to add a listener to the linkedIn 'create post' button through a chrome extension
Now, because I added a timeout, the button is found, but if I run it directly or with a smaller timeout (eg 1000ms) the button is not found
Here's my code:
function findStartPostField() {
const lnCssSelector = '.share-box-feed-entry__trigger'
let button = document.querySelector(lnCssSelector)
console.log('button found ', button)
if (button)
button.addEventListener('click', () => alert('clicked'))
}
setTimeout(findStartPostField, 5000)
console.log('content js loaded, registering message listener');
In my manifest, I tried run_at with document_end and document_idle values without success.
I don't like the idea of having to put a timeout. Is there an event like 'onload' that would trigger when all JS has finished executing (somehow saying the document is rendered and ready)
1. Using message passing.
Firstly register a onload event listener on the extension client side.
Inside the extension's client side onload event listener, send one time message to the content-script.
On the content-script side, for catching incoming messages, register chrome.runtime.onMessage event listener and read the onload type message sent from extension side. Here you can do your DOM mutation.
For example -
popup.js
addEventListener("load", (event) => {
chrome?.tabs?.sendMessage({
type: 'ONDOMLOADED',
sender: 'EXTENSION'
}, function(response) {
console.log(response);
});
});
content_script.js
chrome?.runtime?.onMessage?.addListener(function (request, sender, sendResponse) {
const type = request?.type;
console.assert(request?.sender === 'EXTENSION');
switch(type) {
case 'ONDOMLOADED': {
// DOM ALL THE onDOM CONTENT LODADED THINGS HERE
findStartPostField();
return sendResponse({
type: 'ONDOMLOADED_RESPONSE'
});
}
default:
return;
}
});
2. Using window.onload
content_script.js
window?.onload = function () {
findStartPostField()
}
Hope, it helps you :)
Here's an implementation using MutationObserver.
const onMutation = (mutations) => {
mo.disconnect();
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node) {
if (node.className) {
if (node.className == 'share-box-feed-entry__trigger') {
node.addEventListener('click', () => alert('clicked'))
}
}
}
}
}
observe();
}
const observe = () => {
mo.observe(document, {
subtree: true,
childList: true,
});
}
const mo = new MutationObserver(onMutation);
observe();
Here's an implementation based on wOxxOm's comment.
document.body.addEventListener('click', (e) => {
if (e.target.className == 'share-box-feed-entry__trigger') {
alert('clicked');
}
})
I am working on making a generic logging module for my application and am trying to add session information to each log (requestId/socketId, userId, etc.) But I am running into some issues with logging websockets.
Basically my application has 2 parts: a restAPI (express) and websockets (socket.io)
Both the restAPI and websockets use some of the same functions (database edits etc.), now these functions should log errors or other useful data.
But passing the session information to the logger module will create a lot of overhead and makes the code quite unreadable, so I am looking for a way to save the session information so that the logger can get the information from there.
For the restAPI this was fairly simple using asyncLocalStorage and I was hoping to utilize the same principle for the websockets but I guess its not that simple.
My (partially) working code setup is as follows:
Global context creator (logAsyncContext.ts):
import { AsyncLocalStorage } from "async_hooks";
export const context = new AsyncLocalStorage();
export const createContext = (data: any, callBack: () => any) => {
const store = data;
return context.run(store, () => callBack());
};
This is then used by the middleware of the restAPI and websockets
RestAPI middleware (apiLogContext.ts):
// Import the required modules
import { v4 } from "uuid";
import { Request, Response, NextFunction } from "express";
// Import custom utilities
import { createContext } from "../../utils/logAsyncContext";
import { logger } from "../../utils/logger";
// Generate a unique ID for incoming requests and store in context so logger can access it
export const apiLogContext = (
req: Request,
_res: Response,
next: NextFunction
) => {
const logData = {
api: {
requestId: v4(),
originalUrl: req.originalUrl,
},
};
return createContext(logData, () => debugLog(next));
};
const debugLog = (next: NextFunction) => {
logger. Debug("API log context created");
return next();
};
websocket middleware (wsLogContext.ts):
// Import the required modules
import { v4 } from "uuid";
import { Socket } from "socket.io";
// Import custom utilities
import { createContext } from "../../utils/logAsyncContext";
import { logger } from "../../utils/logger";
// Generate a unique ID for incoming requests and store in context so logger can access it
export const wsLogContext = (socket: Socket, next: () => void) => {
const logData = {
ws: {
socketId: v4(),
nameSpace: socket.nsp.name,
},
};
return createContext(logData, () => debugLog(next));
};
const debugLog = (next: () => void) => {
logger.debug(`WS log context created`);
return next();
};
Now the logger can get the context from logAsyncContext.ts:
import { context } from "./logAsyncContext";
const getStore = () => {
// Get the store from the AsyncLocalStorage
const store = context.getStore();
// If the store is not defined, log an error
if (!store) {
console.log("Store is not defined");
return undefined;
}
return store;
};
export function debug(message: string) {
// Get the context
const store = getStore();
if (!store) {
return;
}
if (isAPILog(store)) {
console.debug(
`DEBUG LOG: ${store.api.requestId} | ${store.api.originalUrl} - ${message}`
);
} else {
console.debug(
`DEBUG LOG: ${store.ws.socketId} | ${store.ws.nameSpace} - ${message}`
);
}
};
This works perfectly for the restAPI but for the websockets its a different story, it does log the initial debug message ("WS log context created") but everything logged after cannot access the store ("Store is not defined")
Now I am sure this is very logical but I don't fully understand the structure of data for websocket connections, so I am asking, am I just making a simple mistake or is this whole setup of logging for websockets incorrect? If so what would be the better way (without needing to pass the session info with every log)?
I faced with same issue.
After shallow investigation, I can suppose following moments:
socket.io middlewares are not the same as in express.(not 100% sure)
There known issue https://github.com/nodejs/node/issues/32330 (closed but with tricky code)
To go forward with AsyncLocalStorage in sockets.io I do next steps:
// context.js
const uuid = require('uuid').v4;
const { AsyncLocalStorage } = require('async_hooks');
const context = new AsyncLocalStorage();
const enterWith = (data) => context.enterWith({traceId: uuid(), ...data });
module.exports = { context, enterWith };
// sockets.js
// I have legacy socket.io v2, your code may be different
io.use(contextMiddleware);
io.use(authSocket);
io.on('connection', (socket) => {
socket.on('USER_CONNECT', async () => {
socket.emit('Exo', `USER_CONNECT`);
try {
// The main solution is here, enter a valid context before actual controller execution
await enterWith({ userId: socket.chatuser });
await userService.createOrUpdateChatUser({ userId: socket.chatuser, customerId });
socket.emit('Exo', `User created`);
} catch (e) {
logger.error(`Create user failure ${e.message}`, { error: e });
socket.emit('Error', e.message);
}
});
Thanks for #bohdan for reminding me that this issue was still unanswered. While his solution works, I will also explain what I did for anyone wondering how to do this using middleware.
What I learned is that WebSockets can be very confusing but quite logical, for me the most important thing to realize was that you cannot use the "same" asyncLocalStorage for a single socket as long as that socket is connected. So I use a different asyncLocalStorage for each event (I will call them stores)
For me there are 4 different "stores" for a websocket connection Which cannot share the same store.
When a connection is made
When an event is received (frontend --> backend)
When an event is sent (backend --> frontend)
When a connection is closed
For all of these types I (mostly) use the same middleware:
import { AsyncLocalStorage } from "async_hooks";
import { Socket } from "socket.io"
import { v4 } from "uuid";
const context = mew AsyncLocalStorage();
const wsLogStore = (socket: Socket, next: () => void) => {
const newData: any = {
// Any data you want to save in the store
// For example socket Id
socketId: socket.id
// I also add an eventId which I can later use in my logging to combine all logs belonging to a single event
eventId: v4()
}
return context.run(newData, () => callBack())
}
#1 For the first type (when a connection is made)
You can use the middleware like this:
// Import the middleware we just created
import wsLogStore from "./wsLogStore"
// io = socketIO server instance (io = new Server)
io.use(wsLogStore)
Now a store will be available everywhere as long as it happens directly after the connection
#2 When a event is received (frontend --> backend)
io.use((socket, next) => {
socket.use((event, next) => {
wsLogStore(socket, () => {
next()
});
});
})
Now everywhere you use socket.on("<any event>") A store will have been created and usable
#3 When an event is sent (backend --> frontend)
Now this one is a little bit different since depending on your implementation this will not be easy, for example when you sent something to a specific room, is it enough to create a single store for the whole room? Or do you want to have a separate one for each socket that is receiving a event? And how do we create a store since we don't have a specific socket available?
For my use case it was absolutely necessary to have a separate store for each socket that is receiving an event.
const sentEventsToSockets = () => {
// Get the sockets you want to send a event to,
// For example, you could get the sockets from a room
const sockets = (Array.from(io.sockets.values()) as Socket[]).filter((socket) => socket.rooms.has("your room"))
for (const socket of sockets) {
wsLogStore(socket, () => {
//Here a separate store for each socket will be available
socket.emit("your event")
})
}
}
#4 When a connection is closed
Sadly, the store we created in step 1 is not available in this case so we would need to create a new one.
io.use((socket, next) => {
socket.on("disconnect", () => {
wsLogStore(socket, () => {
// A separate store will be available here if a connection is closed
});
});
})
Conclusion
While it would be easier if we could create a single store for each socket and use it the whole time, it seems like that is simply not possible.
By saving the socketId in our store we can however combine all data that we need afterwards. For example, in logging.
Note: If you use namespaces the socketId will be different for each namespace, you could use the connection id socket.conn.id which is a unique ID for each socket (no matter which namespace). Why this value is marked as private (if using TS) I have no clue
All of this will of course be slightly different depending on your use case and implementation. For example, if you use namespaces then you need to make sure the middleware is applied in each namespace.
I hope someone finds this helpful and if there are any question about how I do things or how to improve my setup, I would love to hear from you!
I am working on a project in which there is a chat room. The messaging and the joining message is working fine but I don't know how to display a message when someone disconnects. I don't have much knowledge about socket.io as you will see in the code below.
(Edit)
Server Code(Express/Node)
io.on('connection', socket => {
socket.on('message', ({ name, message }) => {
io.emit('message', { name, message })
})
socket.on('join' , (name)=>{
console.log(name);
socket.broadcast.emit("join" , name.usnm.toUpperCase() + " just joined")
})
socket.on('pre_disconnect', (name) => {
console.log(name);
socket.broadcast.emit("pre_disconnect" , name.usnm.toUpperCase() + " just left")
})
})
Client Code(React.js)
const socketRef = useRef()
useEffect(
() => {
socketRef.current = io.connect("http://localhost:4000")
socketRef.current.on("message", ({ name, message }) => {
setChat([ ...chat, { name, message } ])
})
socketRef.current.on("join", (usnm) => {
setChat([ ...chat, {name:usnm} ])
})
socketRef.current.on("pre_disconnect", (usnm) => {
setChat([ ...chat, {name:usnm} ])
})
},
[ chat ]
)
useEffect(
() => {
let usnm = sessionStorage.getItem("User");
socketRef.current.emit("join" ,{usnm});
return () => {
socketRef.current.emit("pre_disconnect" ,{usnm});
socketRef.current.disconnect()
}
},
[ ]
)
In the code above the pre_disconnect doesn't do anything. Code works the same with or without it.
In the server app, define a pre_disconnect event which will be similar to the join event.
io.on('connection', socket => {
socket.on('message', ({ name, message }) => {
io.emit('message', { name, message })
})
socket.on('join', (name) => {
console.log(name);
socket.broadcast.emit("join" , name.usnm.toUpperCase() + " just joined")
})
// Fire this before disconnecting the socket
socket.on('pre_disconnect', (name) => {
console.log(name);
socket.broadcast.emit("pre_disconnect" , name.usnm.toUpperCase() + " just left")
})
})
In the first useEffect hook call, a new socket connection is made and closed everytime there is a chat state update. This means that number of messages in the current session will be generated by amount of socket connects/disconnects. From React Docs
The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its dependencies changes. However, this may be overkill in some cases, like the subscription example from the previous section. We don’t need to create a new subscription on every update, only if the source prop has changed. To implement this, pass a second argument to useEffect that is the array of values that the effect depends on.
Similarly, unless you explicitly need to create and close a new connection on every chat update, avoid it for performance reasons.
In the react app, fire pre_disconnect event with usnm, before calling socketRef.current.disconnect() and add the appropriate handler similar to join.
const socketRef = useRef()
useEffect(() => {
// socketRef.current = io.connect("http://localhost:4000")
if (socketRef.current) {
// Add socketRef as dependency and check if socketRef.current exists then continue with listeners
socketRef.current.on("message", ({ name, message }) => {
setChat([...chat, { name, message }]);
});
socketRef.current.on("join", (usnm) => {
setChat([...chat, { name: usnm }]);
});
// handler for disconnect message
socketRef.current.on("pre_disconnect", (usnm) => {
setChat([...chat, { name: usnm }]);
});
}
// return () => socketRef.current.disconnect()
}, [chat, socketRef]);
useEffect(
() => {
// Here, creates a socket connection only when component renders the first time. Similar to componentDidMount() in class components
socketRef.current = io.connect("http://localhost:4000")
let usnm = sessionStorage.getItem("User");
socketRef.current.emit("join" ,{usnm});
// Disconnect the socket when component is to be unmounted from DOM. Similar to componentWillUnmount() in class components
return () => {
socketRef.current.emit("pre_disconnect" ,{usnm});
socketRef.current.disconnect()
}
},
[ ]
)
Edit:
You were getting that error because socketRef was not initialized. I have added socketRef as a dependency and wrapped the code inside an if condition. Please check the changes.
Using RxJS How can I pass new properties on the observer? So basically I want the prop "customProp" to be available to observable
const { Observable } = require('rxjs');
const observable = Observable.create(function (observer) {
console.log(observer.customProp); //How to get this working?
observer.next(1);
observer.next(2);
observer.next(3);
setTimeout(() => {
observer.next(4);
observer.complete();
}, 1000);
});
console.log('just before subscribe');
observable.subscribe({
next: x => console.log('got value ' + x),
error: err => console.error('something wrong occurred: ' + err),
complete: () => console.log('done'),
customProp: 'Hello World RxJS',
});
console.log('just after subscribe');
---->>> Output
just before subscribe
undefined //How can I get this working, please?
got value 1
got value 2
got value 3
just after subscribe
=> undefined
got value 4
done
Adding more info -- so basically I am trying to create a cold observable in which the producer needs a prop that should come from the subscriber
//Cold observable
var coldObservable = new Observable((observer) => {
var myProducerObj = new MyProducer();
myProducerObj.executeQuery(observer.customProp);
// How could we get customProp over here?
});
Adding usage info ---
coldObservable is DB connection function and customProp is the query that needs to be executed on the DB
This is not possible without extending Observable class and this is not needed. It could be factory function:
const createObservableWithCustomProp = (customProp, observer) => {
return new Observable((observer) => {
var myProducerObj = new MyProducer(customProp);
...
});
};
Usually there is rarely a need to construct observables manually because RxJS API provides rich feature set to create, transform and combine observables.
I created two service send-message.service.ts
import { Injectable } from "#angular/core";
import * as io from 'socket.io-client';
#Injectable()
export class SendMessageService {
private url = 'http://localhost:4000';
private socket = io(this.url);
saveUser(user) {
this.socket.emit('joining-to-chat', user);
}
sendMessage(data) {
this.socket.emit('send-message', data);
}
}
and receive-message.service.ts
import { Injectable } from "#angular/core";
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import * as io from 'socket.io-client';
#Injectable()
export class ReceiveMessageService {
private url = 'http://localhost:4000';
private socket = io(this.url);
getMessage() {
let observable = new Observable(observer => {
this.socket.on('getMessage', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
})
return observable;
}
}
And my server code is look like following:
export default (io) => {
io.on('connect', (socket) => {
var users = [];
socket.on('send-message', (data) => {
io.sockets.in(data.message_to).emit('getMessage', {
text: data.message,
from: data.message_from
})
})
socket.on('joining-to-chat', (data) => {
socket.join(data.username);
users.push(data.username);
io.emit('new-user', users)
})
socket.on('disconnect', () => {
console.log('a user disconnected');
})
})
}
When send-message and getMessage event in same service server code and client code everything work perfectly as my expectation. But if these to event placed in different service I can't emit event two specific client. Only io.emit('getMessage', 'msg') and socket.emit('getMessage', 'msg') work correctly. I am using:
Angula2 v4.3.1
"#types/socket.io-client": "1.4.30",
"socket.io-client": "2.0.3",
"socket.io": "2.0.3",
I want to know how to use socket.io-client in angular2 and what is the best structure of socket.io-client when my app contain lots of different independent module and services?
The server side code for socket joining and emitting event to specific user is pretty well.
But in client side there have two different instance of socket in two different service.
First instance of socket which has been created in SendMessageService that joined with socket and another new socket instance in ReceiveMessageService are not same. So it would not broadcast event as expected.
To make it workable two socket in
getMessage() {
let observable = new Observable(observer => {
this.socket.on('getMessage', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
})
return observable;
}
and
saveUser(user) {
this.socket.emit('joining-to-chat', user);
}
should be same instance of socket.Though the angular component or module are different those will use getMessage and saveUser service methods should be use a common service or a global service.
To create a global service follow the link Making global service