Start and stop Server-sent events notification - javascript

I have 3 Server-sent Events available to a page. Only one viewable at any time. I would like to stop the listener on 2 of the 3 event streams when 1 of them is active.
I have a switch statement testing for which is visible but can not pass the source.close() to my event directly as it is buried in a function:
var firstEventSource = function() {
var eventSrc = new EventSource('firstSTREAM.php');
eventSrc.addEventListener('message', onMessageHandler);
};
I was hoping to have fewer open connections to the server, especially with non-viewed data.
If you have a better suggestion I'm all ears!
Best,
T

function onMessageHandler(event) {
if ("your want to close that EventSource") {
event.target.close();
}
}

This question is hard to answer without more context, but I'll do my best.
You could think of the event resource as a pipe where you push all of your messages, and have the client listen for specific events, effectively multiplexing:
var handler = console.log.bind(console)
, events = new EventSource("/events")
events.addEventListener("new-friend", handler)
events.addEventListener("new-message", handler)
events.addEventListener("new-notification", handler)
This would reduce your connection count to exactly one, and would save you from doing costly reconnects whenever you switch between views. However, it has the drawback of your server pushing (possibly) unnecessary data down the pipe. After all, you're only viewing one message type at a time. You should consider whether this is an actual problem though. If your UI should update, perhaps with some kind of badge notification (like facebook's message or notification icons) then you will need to know about those messages even though the user may not be actively on that particular view. In any event, you should try to keep messages lean for performance sake.
If you can't or won't push all messages down the same pipe, you probably should go with your initial thought of having multiple resources or the ability to query the resource in question, and then opening and closing the connections. Bear in mind though that this could potentially be very costly, as the client could end up hammering the server with requests. Each view change would cause connections to be set up and tore down. It'd look something like this:
/* Assuming jquery is available and with the following html:
* <a class="stream" href="/friends>Friends</a>
* <a class="stream" href="/messages>Messages</a>
* <a class="stream" href="/notifications>Notifications</a>
*/
var currentEvents
, handler = console.log.bind(console)
$("a.stream").on("click", function() {
$el = $(this)
currentEvents && currentEvents.close()
currentEvents = new EventSource($el.attr("href"))
currentEvents.addEventListener("message", handler)
return false
})
In the end, it depends on context. If users aren't going to switch views very often, or the messages are really big, then you might want to go for the second approach. It'll feed less data down the pipe, but create and tear down connections as the user navigates. If the user often switches views however, or you can keep the message size reasonable, then I'd advocate multiplexing, like in the first solution. It'll keep one long-running connection where small messages of different types may be pushed to the client.

Related

How to create a generic "joystick/gamepad event" in Javascript?

Issue:
In the current implementation of modern browsers, (like Firefox or Chrome), there are only two joystick/gamepad events:
gameadConnected
gamepadDisconnected
Since it appears that the original idea behind implementing joystick/gamepad support in the browser was to allow for in-browser games, the joystick was made dependents on the requestAnimationFrame() call to create a game-loop sync'd with v_sync.
However, in other use cases, for example where the joystick is being used to control something remotely over a network or wireless connection, the best case is to only send data when there is something useful to say - did something happen?  Using requestAnimationFrame() floods the interface with potentially useless data.
Unfortunately, there is currently no established interface for triggering gamepad events.&nbsp: (Note, there is some discussion of this very issue over on the Mozilla and W3C forums, so this may, eventually, change.)
Since flooding an industrial device or remote controlled system with useless messages isn't a "best practice" - the question becomes how to generate the equivalent of a gamepad event without flooding the network or stalling the browser in a wait-loop.
Webworkers was a thought, but they cannot be used because they don't have access to the window.event context and cannot interface with the joystick/gamepad.  At least not directly.
In order to handle this efficiently, some method of triggering an "event" that allows data to be sent, only when something of interest happens.
For the benefit of those who may be confronting this same issue, here is the solution I eventually implemented:
=======================
My solution:
This solution is based on the fact that the gamepad's time_stamp attribute only changes when something happens.  (i.e. A button was pressed or a joystick axis was moved,)
Keep track of the gamepad's time_stamp attribute and capture it on the initial gamepad connected event.
Provide a "gateway" condition that surrounds the routine that actually sends the data to the receiving device.
I implemented this in two steps as noted above:
First:
When the gamepad connects, I immediately capture the time_stamp attribute and store it in a variable (old_time).
window.addEventListener("gamepadconnected", (event) => {
js = event.gamepad;
gamepad_connected(); // Gamepad is now connected
old_time = gopigo3_joystick.time_stamp // Capture the initial value of the time_stamp
send_data(gopigo3_joystick) // send it to the robot
Then I do whatever looping and processing of data I need to do.
As a part of that loop, I periodically attempt to send data to the server device with the following code:
function is_something_happening(old_time, gopigo3_joystick) {
if (gopigo3_joystick.trigger_1 == 1 || gopigo3_joystick.head_enable == 1) {
if (old_time != Number.parseFloat(jsdata.timestamp).toFixed()) {
send_data(gopigo3_joystick)
old_time = gopigo3_joystick.time_stamp
}
}
return;
}
function send_data(gpg_data) {
// this sends a gamepad data frame to the robot for interpreting.
[code goes here];
return;
}
The first function, is_something_happening, tests for two qualifying conditions:
A specific joystick button press.  The robot is not allowed to move without a trigger being pressed so no data is sent.&nbsp "head_enable" is another condition that allows messages for head pan-and-tilt commands to be sent.
A change in the time_stamp value.  If the time_stamp value has not changed, nothing of interest has happened.
Both conditions must be satisfied, otherwise the test falls through and immediately returns.
Only if both conditions are met does the send_data() function get called.
This results in a stable interface that always gets called if something of interest has happened, but only if something of interest has happened.
Note:  There are keyboard commands that can be sent, but since they have active events, they can call send_data() by themselves as they only fire when a key is pressed.

How do I use cypress with components that are prefetched or preloaded with webpack?

I'm using Cypress 7.7.0 (also tested on 8.0.0), and I'm running into an interesting race condition. I'm testing a page where one of the first interactions that Cypress does is click a button to open a modal. To keep bundle sizes small, I split the modal into its own prefetched webpack chunk. My Cypress test starts with cy.get('#modal-button').click() but this doesn't load the modal because the modal hasn't finished downloading/loading. It does nothing instead (doesn't even throw any errors to the console). In other words, Cypress interacts with the page too quickly. This was also reproduced with manual testing (I clicked on the button super fast after page load). I have tried setting the modal to be preloaded instead, but that didn't work either.
I am able to solve the problem by introducing more delay between page load and button interaction. For example, inserting any Cypress command (even a cy.wait(0)) before I click on the button fixes the solution. Cypress, however, is known for not needing to insert these brittle solutions. Is there a good way to get around this? I'd like to keep the modal in its own chunk.
FYI: I'm using Vue as my front end library and am using a simple defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue')) to load the modal component. I figure that this problem is general to Cypress though.
There's nothing wrong with cy.wait(0).
All you are doing is handing control from the test to the next process in the JS queue, in this case it's the app's startup script which is presumably waiting to add the click handler to the button.
I recently found that this is also needed in a React hooks app to allow the hook to complete it's process. You will likely also come across that in Vue 3, since they have introduced a hook-like feature.
If you want to empirically test that the event handler has arrived, you can use the method given here (modified for click()) - When Can The Test Start?
let appHasStarted
function spyOnAddEventListener (win) {
const addListener = win.EventTarget.prototype.addEventListener
win.EventTarget.prototype.addEventListener = function (name) {
if (name === 'click') {
appHasStarted = true
win.EventTarget.prototype.addEventListener = addListener // restore original listener
}
return addListener.apply(this, arguments)
}
}
function waitForAppStart() {
return new Cypress.Promise((resolve, reject) => {
const isReady = () => {
if (appHasStarted) {
return resolve()
}
setTimeout(isReady, 0) // recheck "appHasStarted" variable
}
isReady()
})
}
it('greets', () => {
cy.visit('app.html', {
onBeforeLoad: spyOnAddEventListener
}).then(waitForAppStart)
cy.get('#modal-button').click()
})
But note setTimeout(isReady, 0) will probably just achieve the same as cy.wait(0) in your app, i.e you don't really need to poll for the event handler, you just need the app to take a breath.
It seems like your problem is that you're already rendering a button before the code backing it is loaded. As you noticed, this isn't only an issue for fast automated bots, but even a "regular" user.
In short, the solution is to not display the button early, but show a loading dialog instead. Cypress allows waiting for a DOM element to be visible with even a timeout option. This is more robust than a brittle random wait.
I ended up going with waiting for the network to be idle, although there were several options available to me.
The cypress function I used to do this was the following which was heavily influenced by this solution for waiting on the network:
Cypress.Commands.add('waitForIdleNetwork', () => {
const idleTimesInit = 3
let idleTimes = idleTimesInit
let resourcesLengthPrevious
cy.window().then(win =>
cy.waitUntil(() => {
const resourcesLoaded = win.performance.getEntriesByType('resource')
if (resourcesLoaded.length === resourcesLengthPrevious) {
idleTimes--
} else {
idleTimes = idleTimesInit
resourcesLengthPrevious = resourcesLoaded.length
}
return !idleTimes
})
)
})
Here are the pros and cons of the solution I went with:
pros: no need to increase bundle size or modify client code when the user will likely never run into this problem
cons: technically still possible to have a race condition where the click event happens after the assets were downloaded, but before they could all execute and render their contents, but very unlikely, not as efficient as waiting on the UI itself for indication of when it is ready
This was the way I chose solve it but the following solutions would have also worked:
creating lightweight placeholder components to take the place of asychronous components while they download and having cypress wait for the actual component to render (e.g. a default modal that just has a spinner being shown while the actual modal is downloaded in the background)
pros: don't have to wait on network resources, avoids all race conditions if implemented properly
cons: have to create a component the user may never see, increases bundle size
"sleeping" an arbitrary amount (although this is brittle) with cy.wait(...)
pros: easy to implement
cons: brittle, not recommended to use this directly by Cypress, will cause linter problems if using eslint-plugin-cypress (you can disable eslint on the line that you use this on, but it "feels ugly" to me (no hate on anyone who programs that way)

Keep two Meteor collections in sync

I want to keep a ServiceConfiguration collection in sync with a collection of settings. I've (nearly) accomplished this using observeChanges like so:
var handle = Settings.find().observeChanges({
changed: function (id, post) {
var insert = {};
post.hostName && (insert.host = post.hostName);
post.domainName && (insert.domain = post.domainName);
ServiceConfiguration.configurations.update({service: "xmpp"}, insert);
}
});
Meteor.publish("Settings", function() {
this.onStop = function () {
handle.stop();
};
return Settings.find();
});
The problem with this code however is that the publication's onStop method is called straight away, not when the client disconnects. The reason I'm using that callback is because the Meteor docs underline the importance of manually cancelling observeChanges handles, but if I cancel it this way, then I can't actually observe the changes to the collection. The code does however work fine if I don't stop() the handle.
So, can I not stop the handle or would that give me a memory leak? Or how should I go about keeping two Meteor collections in sync?
You can listen for updates to a collection using matb33:collection-hooks. This would be server side:
Settings.after.update(function(userId, doc, fieldNames, modifier, options){
var insert = {};
//...your logic
ServiceConfiguration.configurations.update({service: "xmpp"}, insert);
});
Check out https://github.com/matb33/meteor-collection-hooks
TL/DR - yes, you're fine to remove it.
If I understand correctly, this is a situation in which multiple users can subscribe to the "Settings" publication, potentially change settings in the Settings collection, and you need to automatically propagate these to the ServiceConfiguration collection.
If this is the case then you should not be trying to stop the observer as it's a global construct designed to monitor all changes by any user. The case in which you need to stop an obsever on publication closure is when the observer is run from within the publish function, so there is a new one generated for every connected user. If you didn't stop the observer under those circumstances, the same user could repeatedly connect and disconnect and you'd be left with a potentially unlimited number of running observers and your app would die.
Here however, you would only ever have one observer, which runs independently of the number of subscribing clients. In addition, you can't stop it when any individual publication is stopped, as there will presumably still be other client subscribers who could continue to make changes.
In summary, it's fine to remove the onStop block. Let me know if that doesn't make sense.

Implementing a lock using GM_getValue GM_setValue

I've got a greasemonkey script that, when it runs, checks to see if an update is available, and prompts the user to download the update if so. This normally works fine, except that if a user opens multiple tabs simultaneously (say, on starting the browser, or using "Open All in Tabs" for a bookmark folder), the greasemonkey script will ping the user in each tab simultaneously, which is a bit of a PITA for a user.
I think the only communication channel I have between the instances of the script is GM_setValue/GM_getValue, which allows the instances access to a key/value store.
What I need to do is come up with a locking scheme (let's call it GM_setLock/GM_releaseLock), so I can do the following:
GM_setLock();
const tried_update = GM_getValue(available_version);
GM_setValue(available_version, true);
GM_releaseLock();
if (!tried_update) { prompt_user() }
Without the locking I could have multiple instances in different tabs all read GM_getValue(available_version) before any of them get to GM_setValue(available_version, true), so the user could be pinged multiple times.
The thing is, I don't know how to implement locking off the top of my head if I only have access to (what I'm willing to pretend are) an atomic read and an atomic write operation (and no atomic write and return previous value). Any ideas?
You can't quite do it with that syntax in Greasemonkey, but something like this should do what you want:
Wrap the upgrade check (or whatever), like so:
function UpgradeCheckFunction ()
{
//--- Put payload code here.
alert ("I just ran an an upgrade check?!");
}
.
Then define PerformOnceAcrossTabs(), like so:
function PerformOnceAcrossTabs (sName, oFunction)
{
var OldValue = GM_getValue (sName);
if (OldValue)
{
//--- Optionally also do a timestamp check and clear any "locks" that are X hours old.
return;
}
GM_setValue (sName, new Date().toString() );
//--- run payload function here.
(oFunction)();
//--- Clear "Lock".
GM_deleteValue (sName);
}
.
Then call it like so:
PerformOnceAcrossTabs ("UpgradeCheckLock", UpgradeCheckFunction);

Is JavaScript single threaded? If not, how do I get synchronized access to shared data?

I have a web page with DIVs with a mouseover handler that is intended to show a pop-up information bubble. I don't want more than one info bubble to be visible at a time. But when the user moves the mouse rapidly over two items, I sometimes get two bubbles. This should not happen, because the code for showing a pop-up cancels the previous pop-up.
If this were a multi-threaded system then the problem would be obvious: there are two threads trying to show a pop-up, and they both cancel existing pop-ups then pop up their own pop-ups. But I assumed JavaScript is always run single-threaded, which would prevent this. Am I wrong? Are event handlers running asynchronously, in which case I need synchronized access to shared data, or should I instead be looking for bugs in the library code for cancelling pop-ups?
Edited to add:
The library in question is SIMILE Timeline and its Ajax library;
The event handler does call SimileAjax.DOM.cancelEvent(domEvt), which I assume based on the name cancels the bubbling of events;
Just to make thing s more complicated, what I am actually doing is starting a timeout that if not cancelled by a moustout shows the pop-up, this being intended to prevent pop-ups flickering annoyingly but annoyingly having the reverse effect.
I'll have another poke at it and see if I can work out where I am going wrong. :-)
Yes, Javascript is single-threaded. Even with browsers like Google Chrome, there is one thread per tab.
Without knowing how you are trying to cancel one pop-up from another, it's hard to say what is the cause of your problem.
If your DIVs are nested within one another, you may have an event propagation issue.
I don't know the library you are using, but if you are only trying to display one tooltip of somesort at a time... use a flyweight object. Basically a flyweight is something that is made once and used over and over again. Think of a singleton class. So you call a class statically that when first invoked automatically creates an object of itself and stores it. One this happens every static all references the same object and because of this you don't get multiple tooltips or conflicts.
I use ExtJS and they do tooltips, and message boxes as both flyweight elements. I'm hoping that your frameworks had flyweight elements as well, otherwise you will just have to make your own singleton and call it.
It is single threaded in browsers. Event handlers are running asynchroniously in one thread, non blocking doesn't allways mean multithreaded. Is one of your divs a child of the other? Because events spread like bubbles in the dom tree from child to parent.
Similar to what pkaeding said, it's hard to guess the problem without seeing your markup and script; however, I'd venture to say that you're not properly stopping the event propagation and/or you're not properly hiding the existing element. I don't know if you're using a framework or not, but here's a possible solution using Prototype:
// maintain a reference to the active div bubble
this.oActiveDivBubble = null;
// event handler for the first div
$('exampleDiv1').observe('mouseover', function(evt) {
evt.stop();
if(this.oActiveDivBubble ) {
this.oActiveDivBubble .hide();
}
this.oActiveDivBubble = $('exampleDiv1Bubble');
this.oActiveDivBubble .show();
}.bind(this));
// event handler for the second div
$('exampleDiv2').observe('mouseover'), function(evt) {
evt.stop();
if(this.oActiveDivBubble) {
this.oActiveDivBubble.hide();
}
this.oActiveDivBubble = $('exampleDiv2Bubble');
this.oActiveDivBubble .show();
}.bind(this));
Of course, this could be generalized further by getting all of the elements with, say, the same class, iterating through them, and applying the same event handling function to each of them.
Either way, hopefully this helps.
FYI: As of Firefox 3 there is a change pretty much relevant to this discussion: execution threads causing synchronous XMLHttpRequest requests get detached (this is why the interface doesn't freeze there during synchronous requests) and the execution continues. Upon synchronous request completion, its thread continues as well. They won't be executed at the same time, however relying on the assumption that single thread stops while a synchronous procedure (request) happening is not applicable any more.
It could be that the display isn't refreshing fast enough. Depending on the JS library you are using, you might be able to put a tiny delay on the pop-up "show" effect.
Here's the working version, more or less. When creating items we attach a mouseover event:
var self = this;
SimileAjax.DOM.registerEvent(labelElmtData.elmt, "mouseover", function (elt, domEvt, target) {
return self._onHover(labelElmtData.elmt, domEvt, evt);
});
This calls a function that sets a timeout (pre-existing timeouts for a different item is cancelled first):
MyPlan.EventPainter.prototype._onHover = function(target, domEvt, evt) {
... calculate x and y ...
domEvt.cancelBubble = true;
SimileAjax.DOM.cancelEvent(domEvt);
this._futureShowBubble(x, y, evt);
return false;
}
MyPlan.EventPainter.prototype._futureShowBubble = function (x, y, evt) {
if (this._futurePopup) {
if (evt.getID() == this._futurePopup.evt.getID()) {
return;
} else {
/* We had queued a different event's pop-up; this must now be cancelled. */
window.clearTimeout(this._futurePopup.timeoutID);
}
}
this._futurePopup = {
x: x,
y: y,
evt: evt
};
var self = this;
this._futurePopup.timeoutID = window.setTimeout(function () {
self._onTimeout();
}, this._popupTimeout);
}
This in turn shows the bubble if it fires before being cancelled:
MyPlan.EventPainter.prototype._onTimeout = function () {
this._showBubble(this._futurePopup.x, this._futurePopup.y, this._futurePopup.evt);
};
MyPlan.EventPainter.prototype._showBubble = function(x, y, evt) {
if (this._futurePopup) {
window.clearTimeout(this._futurePopup.timeoutID);
this._futurePopup = null;
}
...
SimileAjax.WindowManager.cancelPopups();
SimileAjax.Graphics.createBubbleForContentAndPoint(...);
};
This seems to work now I have set the timeout to 200 ms rather than 100 ms. Not sure why too short a timeout causes the multi-bubble thing to happen, but I guess queuing of window events or something might still be happening while the newly added elements are being laid out.

Categories