Rx.js, Chrome Messaging API - google-chrome-extension

After another part of the extension has sent a message using this code
chrome.runtime.sendMessage({greeting: "hello"});
Any Rx.js experts out there who can take this messaging API
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
and wrap it in an Observable that emits new messages and allows a sendResponse?
Simply emitting incoming messages as an Observable is very easy.
const MessagingObservable = Rx.Observable.create(observer => {
chrome.runtime.onMessage.addListener(listener);
function listener(request, sender, sendResponse) {
observer.next(request);
}
return () => {
chrome.runtime.onMessage.removeListener(listener);
};
});
But how to bind the sendResponse callback?

You can use the built-in fromEventPattern function to create an observable, like this:
const messages = Rx.Observable.fromEventPattern(
handler => chrome.runtime.onMessage.addListener(handler),
handler => chrome.runtime.onMessage.removeListener(handler),
(request, sender, sendResponse) => ({ request, sender, sendResponse })
);
Note that the call to fromEventPattern includes a result selector, so that values emitted by the observable contain the request, the sender, and the sendResponse, which you'd use like this:
messages.subscribe(({ request, sender, sendResponse }) => {
console.log(request);
sendResponse(/* ... whatever ... */);
});
To support calling sendResponse asynchronously, the listener needs to be able to return true. This can be done by wrapping the handler, like this:
const messages = Rx.Observable.fromEventPattern(
handler => {
const wrapper = (request, sender, sendResponse) => {
const event = { async: false, request, sender, sendResponse };
handler(event);
return event.async;
};
chrome.runtime.onMessage.addListener(wrapper);
return wrapper;
},
(handler, wrapper) => chrome.runtime.onMessage.removeListener(wrapper)
);
And you'd use it like this:
messages.subscribe(event => {
console.log(event.request);
event.async = true;
setTimeout(() => event.sendResponse(/* ... whatever ... */), 1000);
});

Related

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

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

piping incoming http get request to outgoing write request on nodejs not working as expected

I'm trying to build a basic pub/sub event emitter and I'm having trouble understanding how I can pipe an incoming event with payload to all of the event's subscribers. This is what I have:
app.post("/events", (req, res) => {
const { type } = req.body;
if (!type) {
res.status(404).send(`no event found`);
return;
}
if (!eventTypeToUrls[type]) {
res.status(404).send(`no event ${type} subscribed to`);
return;
}
let count = 0;
const urls = eventTypeToUrls[type];
urls.forEach((url) => {
console.log("sending payload to >>", url);
const writeStream = http.request(
url + "/events",
{ method: "post" },
() => {
count += 1;
if (count === urls.length) {
res.status(201).send("ok");
}
}
);
writeStream.on("error", (e) => res.status(401).send(e));
req.pipe(writeStream);
});
});
I saw that I might in a non-piping setting would need to call res.write with the serialized payload, but since I'm piping from a readable stream to a writable stream, I'm not sure exactly how that would change. I imagine I wouldn't need to call res.end either. thanks.
The problem is that the readable req-stream will already have been consumed, once the request-handler callback is hit, thus you cannot pipe it to a writeable stream.
However, if all you want to do is to send http-requests to all subscriber urls, you can use Promise.all to await the non-sequential processing of the requests and simply do res.status(..).send(..) once all promises are fulfilled. Something like this (I'm using superagent here as the http-request library):
app.post('/events', async (req, res) => {
// ...
try {
await Promise.all(urls.map(url => {
return superagent
.post(url + '/events')
.send(req.body);
}));
res.status(201).send("ok");
} catch (err) {
res.status(500).end('something went wrong ...');
}
});

How to abort async function when 'stop' event has been emitted

I trying to make a puppeteer.js bot to be able to pause and resume its work.
In general, i have a class with a dozen of async methods, event emitter and a property called 'state' with setter to change it. When I have event 'stop', I want some async functions to be aborted. How can I achieve this?
I thought i need to observe when this.state becomes 'stop', and run return; but hadn't found any solution.
Then I decided to try to set a handler on an event which changes state to 'stop', but I cannot abort async functions from the handler on the stop event.
constructor() {
this.state = 'none';
this.emiter = new events.EventEmitter();
this.setHandler('stop', () => this.stop());
this.setHandler('resume', () => this.resume());
this.setHandler('onLoginPage', () => this.passAuth());
// ...
// And dozen of other states with its handlers
}
stop= () => this.setState('stoped', true);
resume = () => this.setState(this.getPreviousState());
getPreviousState = () => ...
// Just an example of a state handler. It has async calls as well
// I want to abort this function when event 'stop' is emitted
#errorCatcher()
async passAuth() {
const { credentials } = Setup.Instance;
await this.page.waitForSelector(LOGIN);
await typeToInput(this.page, EMAIL_INPUT, credentials.login);
await typeToInput(this.page, PWD_INPUT, credentials.pass);
await Promise.all([
await this.page.click(LOGIN),
this.page.waitForNavigation({ timeout: 600000 }),
]);
await this.page.waitFor(500);
await DomMutations.setDomMutationObserver(this.page, this.socketEmitter);
// ...
// And dozen of handlers on corresponding state
setState(nextState, resume) {
// Avoiding to change state if we on pause.
// But resume() can force setstate with argument resume = true;
if (this.state === 'stoped' && !resume) return false;
console.log(`\nEmmited FSM#${nextState}`);
this.emiter.emit(`FSM#${nextState}`);
}
setHandler(state, handler) {
this.emiter.on(`FSM#${state}`, async () => {
this.state = state;
console.log(`State has been changed: ${this.getPreviousState()} ==> ${this.state}. Runnig handler.\n`);
//
// On the next line, we run a corresponding handler func,
// like passAuth() for state 'onLoginPage'. It has has to be aborted
// if emiter gets 'FSM#stoped' event.
//
await handler();
});
}
}```
I expect the async functions to be aborted when event emitter emits 'stop';
It is impossible to do it natively.
Alternatively, there are two other way to do it.
check your state after any call of await, for example:
class Stated {
async run() {
await foo()
if(this.stopped) return
await bar()
if(this.stopped) return
await done()
}
}
const s = new Stated()
s.run()
use generator with custom wrapper rather than async/await.
// the wrapper
function co(gen, isStopped = () => false) {
return new Promise((resolve, reject) => {
if (!gen || typeof gen.next !== 'function') return resolve(gen)
onFulfilled()
function onFulfilled(res) {
let ret
try {
ret = gen.next(res)
} catch (e) {
return reject(e)
}
next(ret)
}
function onRejected(err) {
let ret
try {
ret = gen.throw(err)
} catch (e) {
return reject(e)
}
next(ret)
}
function next(ret) {
if (ret.done || isStopped()) return resolve(ret.value)
Promise.resolve(ret.value).then(onFulfilled, onRejected)
}
});
}
// the following is your code:
class Stated {
* run() {
yield foo()
yield bar()
yield done()
}
}
const s = new Stated()
co(s.run(), () => s.stopped)

Knex event on transaction success

I have a function that takes a transaction object as an argument. Can this function subscribe to an event that fires when the transaction is commited?
function createUser (data, trx) {
trx.on('success', .. )
return User.create(data, { transacting: trx })
}
I don't see anything like that in the source, if not inner/outer transaction can be used somehow.
https://github.com/tgriesser/knex/blob/master/src/transaction.js
I never found better solution. But transaction is event emmiter so you can override default knex functions to emit your custom event.
Override commit to fire event.
knex = require('knex')({...});
const _transaction = knex.transaction;
knex.transaction = (cb) => {
return _transaction(trx => {
const _commit = trx.commit;
trx.commit =async (conn, value) => {
const out = await _commit(conn, value);
trx.emit('commit');
return out;
}
return cb(trx);
})
};
Listen to commit event anywhere in code
knex.transaction(async trx => {
trx.on('commit', async () => {
// fired after commit is done
});
await trx.select().from('users').update({...});
})

Why this function is returning response as null

I wrote a function on event click as below
$('#ClickMe').live('click', function () {
chrome.extension.sendRequest({ method: "getT" }, function (response) {
alert(response.data); // Displaying undefined..
});
});
in background page..
function wish(){
return "Hey...";
}
chrome.extension.onRequest.addListener(function(request, sender, sendResponse){
if (request.method == "getT"){
sendResponse({data: wish()});
}
else
sendResponse({});
});
I cannot get the response in Content Script.Please help me on this.
You cannot send functional parameters like that within a JSON object you would need to instantiate it first, and then pass it as a variable not as a function because it will treat it as a Closure, so when it does the serialization, it will not include that.
function wish(){
return "Hey...";
}
chrome.extension.onRequest.addListener(function(request, sender, sendResponse){
if (request.method == "getT"){
var data = wish();
sendResponse({data: data});
}
else
sendResponse({});
});
The above snippet should work.

Resources