chrome extension content.js file loads too early: unable to find an element in DOM - google-chrome-extension

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');
}
})

Related

How to display message when user disconnects from chat room in socket.io

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.

mainWindow.webContents.send does not work

I am trying to send response from python axios.post to ipcRenderer but mainWindow.webContents does not send the response to renderer and sometime the response from axios is delayed
please help me with this
main.js
axios.post(`${pythonHost}/send-request/`,options)
.then(function(response){
mainWindow.webContents.on('did-finish-load', function () {
mainWindow.webContents.send('request_status:success',JSON.stringify(response.data))
});
});
index.js
ipcRenderer.on('request_status:success',(e,response)=>{
\\
})
In your code, mainWindow.webContents.send will never run unless did-finish-load is fired. This event will only fire when the page has loaded and it won't fire again
So in your case, you are just waiting forever for an event that won't get fired and you are never executing mainWindow.webContents.send
axios.post(`${pythonHost}/send-request/`, options).then(function (response) {
mainWindow.webContents.on("did-finish-load", function () {
// this will not run until the "did-finish-load" fires !
mainWindow.webContents.send(
"request_status:success",
JSON.stringify(response.data)
);
});
});
You probably want to do something like this instead
const { once } = require("events");
let didFinishLoad = false;
mainWindow.webContents.on("did-finish-load", function () {
didFinishLoad = true;
});
axios
.post(`${pythonHost}/send-request/`, options)
.then(async function (response) {
if (!didFinishLoad) {
// wait until did-finish-load is fired
await once(mainWindow.webContents, "did-finish-load");
}
mainWindow.webContents.send(
"request_status:success",
JSON.stringify(response.data)
);
});

How to test keyup with addEventListener

I want to test handleKeyPress method, but when i simulate keyup, then i have this error: Expected spy to have been called, but it was not called.
//App.js
componentDidMount() {
document.addEventListener('keyup', this.handleKeyPress)
}
handleKeyPress = (event) => {
if (event.keyCode === 38) {
this.setState({
up: true
})
}
}
//App.test.js
it('check handleKeyPress with keyup', ()=>{
let instance = wrapper.instance()
let handleKeyPress = spyOn(instance, 'handleKeyPress')
wrapper.simulate('keyup', {keyCode: 38})
expect(handleKeyPress).toHaveBeenCalled()
})
You are simulating the keyup event on your component wrapper but adding the keyup event listener to the document object. The Common Gothcas section mentions some stuff about event propagation not working as you would expect.
The only way I can think to get this working would be to spy on document.addEventListener and manually call the handler in your test file.
it('check handleKeyPress with keyup', () => {
// save keyup event handler added to document
let keyUpHandler;
document.addEventListener = jest.fn((event, handler) => {
if (event === 'keyup') {
keyUpHandler = handler;
}
});
// render component
wrapper = shallow(...);
let instance = wrapper.instance()
let handleKeyPress = jest.spyOn(instance, 'handleKeyPress')
// call the keyup handler with the event data
keyUpHandler({ keyCode: 38 })
expect(handleKeyPress).toHaveBeenCalled()
})

How to wait for an API call using subscribe to finish in ngOnInit?

Actual Log order:
('ngOnInit started')
('after me aaya', this.policydetails)
('Here', Object.keys(this.policy).length)
Expected Log order:
('ngOnInit started')
('Here', Object.keys(this.policy).length)
('after me aaya', this.policydetails)
Component.ts file snippet below:
ngOnInit() {
console.log('ngOnInit started');
this.route.params.subscribe(params => {
this.getPoliciesService.getPolicyDetails(params.policyNo)
.subscribe((data: PoliciesResponse) => {
this.policy = data.data[0];
this.flattenPolicy();
console.log('Here', Object.keys(this.policy).length);
});
});
this.makePolicyTable();
}
ngAfterViewInit() {
console.log('after me aaya', this.policydetails);
const table = this.policydetails.nativeElement;
table.innerHTML = '';
console.log(table);
console.log(this.table);
table.appendChild(this.table);
console.log(table);
}
Service.ts file snippet below:
getPolicyDetails(policyNo) {
const serviceURL = 'http://localhost:7001/getPolicyDetails';
console.log('getPolicyDetails service called, policyNo:', policyNo);
const params = new HttpParams()
.set('policyNo', policyNo);
console.log(params);
return this.http.get<PoliciesResponse>(serviceURL, {params} );
}
JS file snippet corresponding to the API call below:
router.get('/getPolicyDetails', async function(req, res) {
let policyNo = (req.param.policyNo) || req.query.policyNo;
console.log('policyNo', typeof policyNo);
await helper.getPolicyDetails({'policyNo' : policyNo},
function(err, data) {
console.log(err, data)
if (err) {
return res.send({status : false, msg : data});
}
return res.send({status : true, data : data});
});
});
Can anyone please suggest where exactly do i need to async-await for expected log order?
If you want this.makePolicyTable() to be called only after the web request (getPolicyDetails) completes, then you should place that call inside of the .subscribe() block:
ngOnInit() {
console.log('ngOnInit started');
this.route.params.subscribe(params => {
this.getPoliciesService.getPolicyDetails(params.policyNo)
.subscribe((data: PoliciesResponse) => {
this.policy = data.data[0];
this.flattenPolicy();
console.log('Here', Object.keys(this.policy).length);
this.makePolicyTable();
});
});
}
You'll probably also want to move the table logic that's in ngAfterViewInit() inside the subscribe() block, too.
Basically, any logic that needs to wait for the asynchronous call to complete should be triggered inside the .subscribe() block. Otherwise, as you're seeing, it can be run before the web request gets back.
Finally, I would move this web service call into ngAfterViewInit() instead of ngOnInit(). Then you can be sure that the Angular components and views are all set up for you to manipulate them when the web service call completes.
You could also set a flag variable to false in the component and then when async calls finishes set it to true and render the HTML based on that flag variable with *ngIf syntax.

observer create multiple message with socket.io in angular 2

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

Resources