Detecting socket.io events in an HTML game - html

I'm attempting to use puppeteer to detect certain socket.io events emitted by the game, such as these:
socket.on("setStartTime", socket_onSetStartTime);
socket.on("nextTurn", socket_onNextTurn);
socket.on("livesLost", socket_onLivesLost);
socket.on("bonusAlphabetCompleted", socket_onBonusAlphabetCompleted);
socket.on("setPlayerWord", socket_onSetPlayerWord);
socket.on("failWord", socket_onFailWord);
socket.on("correctWord", socket_onCorrectWord);
socket.on("happyBirthday", socket_onHappyBirthday);
Is there a way to do this with puppeteer or something else in node?
Edit: Solution was to create a CDP session, as listed here.
Example:
const cdp = await page.target().createCDPSession();
await cdp.send("Network.enable");
await cdp.send("Page.enable");
cdp.on("Network.webSocketFrameReceived", handleResponse); // Fired when WebSocket message is received.
cdp.on("Network.webSocketFrameSent", handleResponse); // Fired when WebSocket message is sent.
function handleResponse(packet) {
//do stuff with packet
}

Related

Cypress: is there an event listener for notifications in the app

In principle, the app under test could throw at any time Error notifications (usually when something is not working as it should: server side). My problem is that my cypress test does not fail over such error messages.
Is it possible to configure a listener in cypress for such events? It would basically always listen if something like a message box pops up.
Eg. listening for:
cy.contains('[data-e2e-notification-message-text]', 'ERROR: ')
You're talking about the difference between active and passive element checking.
Generally speaking, active waiting for the notification is better
cy.contains('[data-e2e-notification-message-text]', 'ERROR: ', {timeout: 7000})
.should('not.exist')
than passive waiting
cy.on('notification', (message) => { // NOT REAL CODE - for illustration
if (message.includes('ERROR: ')) {
throw 'Notification occurred' // fail the test
}
})
because the chain of events involves asynchronous call from the backend, which can vary in timing. If the test ends before the notification arrives, you get a false positive test.
You can set up an intercept if the action is request/response style, e.g
cy.intercept(...).as('notification') // listen
cy.get(button).click() // action
cy.wait('#notification') // assert
Add a stub if the live server is slow
cy.intercept(url, { notification: 'Error: ' }) // immediately fake response
cy.get(button).click() // action
cy.contains('[data-e2e-notification-message-text]', 'ERROR: ') // assert
Or don't explicitly wait
cy.intercept(url, (req) => {
req.on('response', (res) => {
if (res.body.includes('Error:')) {
throw 'Notification of error' // fail the test
}
})
cy.get(button).click() // action
But also a chance of false positive result depending on timing.
If you did have a use case for periodic checking for an element, you could tap into the command:end event.
You can only do static (jQuery) DOM querying in an event handler (no cy.() commands).
// after every command look for notification
Cypress.on('command:end', () => {
const notification = Cypress.$('[data-e2e-notification-message-text]:contains(ERROR:)')
if (notification.length) {
throw 'Notification of error happened' // fail the test
}
})
Same caveat applies - this could be flaky if the test is faster than the notification.

Slack webhooks cause cls-hooked request context to orphan mysql connections

The main issue:
We have a lovely little express app, which has been crushing it for months with no issues. We manage our DB connections by opening a connection on demand, but then caching it "per request" using the cls-hooked library. Upon the request ending, we release the connection so our connection pool doesn't run out. Classic. Over the course of months and many connections, we've never "leaked" connections. Until now! Enter... slack! We are using the slack event handler as follows:
app.use('/webhooks/slack', slackEventHandler.expressMiddleware());
and we sort of think of it like any other request, however slack requests seem to play weirdly with our cls-hooked usage. For example, we use node-ts and nodemon to run our app locally (e.g. you change code, the app restarts automatically). Every time the app restarts locally on our dev machines, and you try and play with slack events, suddenly when our middleware that releases the connection tries to do so, it thinks there is nothing in session. When you then use a normal endpoint... it works fine and essentially seems to reset slack to working okay again. We are now scared to go to prod with our slack integration, because we're worried our slack "requests" are going to starve our connection pool.
Background
Relevant subset of our package.json:
{
"#slack/events-api": "^2.3.2",
"#slack/web-api": "^5.8.0",
"express": "~4.16.1",
"cls-hooked": "^4.2.2",
"mysql2": "^2.0.0",
}
The middleware that makes the cls-hooked session
import { session } from '../db';
const context = (req, res, next) => {
session.run(() => {
session.bindEmitter(req);
session.bindEmitter(res);
next();
});
};
export default context;
The middleware that releases our connections
export const dbReleaseMiddleware = async (req, res, next) => {
res.on('finish', async () => {
const conn = session.get('conn');
if (conn) {
incrementConnsReleased();
await conn.release();
}
});
next();
};
the code that creates the connection on demand and stores it in "session"
const poolConn = await pool.getConnection();
if (session.active) {
session.set('conn', poolConn);
}
return poolConn;
the code that sets up the session in the first place
export const session = clsHooked.createNamespace('our_company_name');
If you got this far, congrats. Any help appreciated!
Side note: you couldn't pay me to write a more confusing title...
Figured it out! It seems we have identified the following behavior in the node version of slack's API (seems to only happen on mac computers... sometimes)
The issue is that this is in the context of an express app, so Slack is managing the interface between its own event handler system + the http side of things with express (e.g. returning 200, or 500, or whatever). So what seems to happen is...
// you have some slack event handler
slackEventHandler.on('message', async (rawEvent: any) => {
const i = 0;
i = i + 1;
// at this point, the http request has not returned 200, it is "pending" from express's POV
await myService.someMethod();
// ^^ while this was doing its async thing, the express request returned 200.
// so things like res.on('finished') all fired and all your middleware happened
// but your event handler code is still going
});
So we ended up creating a manual call to release connections in our slack event handlers. Weird!

How to get all console messages with puppeteer? including errors, CSP violations, failed resources, etc

I am fetching a page with puppeteer that has some errors in the browser console but the puppeteer's console event is not being triggered by all of the console messages.
The puppeteer chromium browser shows multiple console messages
However, puppeteer only console logs one thing in node
Here is the script I am currently using:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.on('console', msg => console.log('PAGE LOG:', msg.text));
await page.goto('https://pagewithsomeconsoleerrors.com');
await browser.close();
})();
Edit: As stated in my comment below, I did try the page.waitFor(5000) command that Everettss recommended but that didn't work.
Edit2: removed spread operator from msg.text as it was there by accident.
Edit3: I opened an issue on github regarding this with similar but different example screenshots: https://github.com/GoogleChrome/puppeteer/issues/1512
The GitHub issue about capturing console erorrs includes a great comment about listening to console and network events. For example, you can register for console output and network responses and failures like this:
page
.on('console', message =>
console.log(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`))
.on('pageerror', ({ message }) => console.log(message))
.on('response', response =>
console.log(`${response.status()} ${response.url()}`))
.on('requestfailed', request =>
console.log(`${request.failure().errorText} ${request.url()}`))
And get the following output, for example:
200 'http://do.carlosesilva.com/puppeteer/'
LOG This is a standard console.log message
Error: This is an error we are forcibly throwing
at http://do.carlosesilva.com/puppeteer/:22:11
net::ERR_CONNECTION_REFUSED https://do.carlosesilva.com/http-only/sample.png
404 'http://do.carlosesilva.com/puppeteer/this-image-does-not-exist.png'
ERR Failed to load resource: the server responded with a status of 404 (Not Found)
See also types of console messages received with the console event and response, request and failure objects received with other events.
If you want to pimp your output with some colours, you can add chalk, kleur, colorette or others:
const { blue, cyan, green, magenta, red, yellow } = require('colorette')
page
.on('console', message => {
const type = message.type().substr(0, 3).toUpperCase()
const colors = {
LOG: text => text,
ERR: red,
WAR: yellow,
INF: cyan
}
const color = colors[type] || blue
console.log(color(`${type} ${message.text()}`))
})
.on('pageerror', ({ message }) => console.log(red(message)))
.on('response', response =>
console.log(green(`${response.status()} ${response.url()}`)))
.on('requestfailed', request =>
console.log(magenta(`${request.failure().errorText} ${request.url()}`)))
The examples above use Puppeteer API v2.0.0.
Easiest way to capture all console messages is passing the dumpio argument to puppeteer.launch().
From Puppeteer API docs:
dumpio: <boolean> Whether to pipe the browser process stdout and stderr
into process.stdout and process.stderr. Defaults to false.
Example code:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
dumpio: true
});
...
})();
You need to set multiple listeners if you want to catch everything. The console event is emitted when javascript within the page calls a console API message (like console.log).
For a full list of the console API take a look at the docs for console on MDN:
https://developer.mozilla.org/en-US/docs/Web/API/Console
The reason you need multiple listeners is because some of what is being logged in the image you posted is not happening within the page.
So for example, to catch the first error in the image, net:: ERR_CONNECTION_REFUSED you would set the listener like so:
page.on('requestfailed', err => console.log(err));
Puppeteer's documentation contains a full list of events. You should take a look at the documentation for the version you are using and look at the different events the Page class will emit as well as what those events will return. The example I've written above will return an instance of Puppeteer's request class.
https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page
I extended Ferdinand's great comment code with descriptive errors text from JSHandle consoleMessage object in next comment. So you can catch all the errors from the browser and read all the description like in browser console.
Check it out there https://stackoverflow.com/a/66801550/9026103

net::ERR_CONNECTION_RESET with service worker in Chrome

I have a very simple service worker to add offline support. The fetch handler looks like
self.addEventListener("fetch", function (event) {
var url = event.request.url;
event.respondWith(fetch(event.request).then(function (response) {
//var cacheResponse: Response = response.clone();
//caches.open(CURRENT_CACHES.offline).then((cache: Cache) => {
// cache.put(url, cacheResponse).catch(() => {
// // ignore error
// });
//});
return response;
}).catch(function () {
// check the cache
return getCachedContent(event.request);
}));
});
Intermittently we are seeing a net::ERR_CONNECTION_RESET error for a particular script we load into the page when online. The error is not coming from the server as the service worker is picking up the file from the browser cache. Chrome's network tab shows the service worker has successfully fetched the file from the disk cache but the request from the browser to the service worker shows as (failed)
Does anyone know the underlying issue causing this? Is there a problem with my service worker implementation?
This is likely due to a bug in Chrome (and potentially other browsers as well) that could result in a garbage collection event removing a reference to the response stream while it's still being read.
Its fix in Chrome is being tracked at https://bugs.chromium.org/p/chromium/issues/detail?id=934386.

Chrome Push Notification: This site has been updated in the background

While implementing the chrome push notification, we were fetching the latest change from our server. While doing so, the service-worker is showing an extra notification with the message
This site has been updated in the background
Already tried with the suggestion posted here
https://disqus.com/home/discussion/html5rocks/push_notifications_on_the_open_web/
But could not find anything useful till now. Is there any suggestion ?
Short Answer: You should use event.waitUntil and pass a promise to it, which returns showNotification eventually. (if you have any other nested promises, you should also return them.)
I was expriencing the same issue but after a long research I got to know that this is because delay happen between PUSH event and self.registration.showNotification(). I only missed return keyword before self.registration.showNotification()`
So you need to implement following code structure to get notification:
var APILINK = "https://xxxx.com";
self.addEventListener('push', function(event) {
event.waitUntil(
fetch(APILINK).then(function(response) {
return response.json().then(function(data) {
console.log(data);
var title = data.title;
var body = data.message;
var icon = data.image;
var tag = 'temp-tag';
var urlOpen = data.URL;
return self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
});
})
);
});
Minimal senario:
self.addEventListener('push', event => {
const data = event.data.json();
event.waitUntil(
// in here we pass showNotification, but if you pass a promise, like fetch,
// then you should return showNotification inside of it. like above example.
self.registration.showNotification(data.title, {
body: data.content
})
);
});
I've run into this issue in the past. In my experience the cause is generally one of three issues:
You're not showing a notification in response to the push
message. Every time you receive a push message on the device, when
you finish handling the event a notification must be left visible on
the device. This is due to subscribing with the userVisibleOnly:
true option (although note this is not optional, and not setting it
will cause the subscription to fail.
You're not calling event.waitUntil() in response to handling the event. A promise should be passed into this function to indicate to the browser that it should wait for the promise to resolve before checking whether a notification is left showing.
For some reason you're resolving the promise passed to event.waitUntil before a notification has been shown. Note that self.registration.showNotification is a promise and async so you should be sure it has resolved before the promise passed to event.waitUntil resolves.
Generally as soon as you receive a push message from GCM (Google Cloud Messaging) you have to show a push notification in the browser. This is mentioned on the 3rd point in here:
https://developers.google.com/web/updates/2015/03/push-notificatons-on-the-open-web#what-are-the-limitations-of-push-messaging-in-chrome-42
So it might happen that somehow you are skipping the push notification though you got a push message from GCM and you are getting a push notification with some default message like "This site has been updated in the background".
This works, just copy/paste/modify. Replace the "return self.registration.showNotification()" with the below code. The first part is to handle the notification, the second part is to handle the notification's click. But don't thank me, unless you're thanking my hours of googling for answers.
Seriously though, all thanks go to Matt Gaunt over at developers.google.com
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = 'YOUR_ICON';
var tag = 'simple-push-demo-notification-tag';
var data = {
doge: {
wow: 'such amaze notification data'
}
};
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: data
})
);
});
self.addEventListener('notificationclick', function(event) {
var doge = event.notification.data.doge;
console.log(doge.wow);
});
I was trying to understand why Chrome has this requirement that the service worker must display a notification when a push notification is received. I believe the reason is that push notification service workers continue to run in the background even after a user closes the tabs for the website. So in order to prevent websites from secretly running code in the background, Chrome requires that they display some message.
What are the limitations of push messaging in Chrome?
...
You have to show a notification when you receive a push message.
...
and
Why not use Web Sockets or Server-Sent Events (EventSource)?
The advantage of using push messages is that even if your page is closed, your service worker will be woken up and be able to show a notification. Web Sockets and EventSource have their connection closed when the page or browser is closed.
If you need more things to happen at the time of receiving the push notification event, the showNotification() is returning a Promise. So you can use the classic chaining.
const itsGonnaBeLegendary = new Promise((resolve, reject) => {
self.registration.showNotification(title, options)
.then(() => {
console.log("other stuff to do");
resolve();
});
});
event.waitUntil(itsGonnaBeLegendary);
i was pushing notification twice, once in the FCM's onBackgroundMessage()
click_action: "http://localhost:3000/"
and once in self.addEventListener('notificationclick',...
event.waitUntil(clients.matchAll({
type: "window"
}).then...
so i commented click_action, ctrl+f5 to refresh browsers and now it works normal