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. : (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.  "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.
Related
I have a problem understanding the documentation for the WebExtensions notification.onClicked event.
Ultimately, I'm trying to get the text of the notification copied to the clipboard when you click on it. However, right now I am having a problem understanding the callback thing, or where I have to insert the notification.onClicked function.
At the moment, I don't know why the notification.onClicked listener does nothing.
My code (all the code needed to demonstrate the problem as a WebExtension Firefox add-on):
manifest.json
{
"description": "Test Webextension",
"manifest_version": 2,
"name": "Σ",
"version": "1.0",
"permissions": [
"<all_urls>",
"notifications",
"webRequest"
],
"background": {
"scripts": ["background.js"]
}
}
background.js
'use strict';
function logURL(requestDetails) {
notify("Testmessage");
chrome.notifications.onClicked.addListener(function() {
console.log("TEST TEST");
});
}
function notify(notifyMessage) {
var options = {
type: "basic",
iconUrl: chrome.extension.getURL("icons/photo.png"),
title: "",
message: notifyMessage
};
chrome.notifications.create("ID123", options);
}
chrome.webRequest.onBeforeRequest.addListener(
logURL, {
urls: ["<all_urls>"]
}
);
First, you need to be testing this in Firefox 47.0+, as support for chrome.notifications.onClicked() was added in version 47.0. While this is probably not your problem, it is one contributing possibility.
There are multiple issues with your code. Some are in your code, but primarily you are running into a Firefox bug.
Firefox Bug:
Your primary issue is that you are running into a Firefox bug where Firefox gets confused if you try to create notifications too rapidly. Thus, I have implemented a notification queue and rate limited the creation of notifications. What is "too rapidly" is probably both OS and CPU dependent, so you are best off erroring on the side of caution and set the delay between calls to chrome.notifications.create() to a higher value. in the code below, the delay is 500ms. I have added a note regarding this issue in the chrome.notifications.create() page on MDN and on the Chrome incompatibilities page.
Adding multiple copies of the same listener:
The main thing that you are doing wrong in your code is that you are adding an anonymous function as a listener, using chrome.notifications.onClicked.addListener(), multiple times to the same event. This is a generic issue with event handlers. When you use an anonymous function it is a different actual function each time you are trying to add it, so the same functionality (in multiple identical functions) gets added multiple times. You should not be adding functions, which do the exact same thing, multiple times to the same event. Doing so is almost always an error in your program and results in unexpected operation.
In this case, the multiple functions would have ended up outputing multiple lines of TEST TEST to the console each time the user clicked on a notification. The number of lines output per click would increase by one for each web request which resulted in a call to logURL.
The way to prevent doing this is to be sure to add the listener only once. If you are using an anonymous function, you can only do this by being sure you only execute the addListener (or addEventlistener) once (usually by only adding the listener from your main code (not from within a function), or from a function that is only called once. Alternately, you can name/define your listener function directly within the global scope (or other scope accessible to all places where you try to add the listener) (e.g. function myListener(){...}). Then, when you are adding myListener you are always referring to the same exact function which JavaScript automatically prevents you from adding in the same way to the same event more than once.
It should be noted that if you are trying to add a anonymous function as a listener from another listener, you are almost always doing something wrong. Adding copies of identical anonymous listeners multiple times to the same event is a common error.
Access to the notification text:
While you do not implement anything regarding using the text of the notification, you state that you want to add the text of the notification to the clipboard when the user clicks on the notification. You can not obtain the notification text from any portion of the chrome.notifications API. Thus, you have to store that information yourself. The code below implements an Object to do that so the text can be accessed in the chrome.notifications.onClicked() handler.
Example code:
The code below implements what I believe you desire. It is just creating and clicking the notification while having access to the notification text in the chrome.notifications.onClicked() listener. It does not implement the part about putting the text into the clipboard, as that was not actually implemented in the code in your Question. I have added liberal comments to the code to explain what is happening and provided quite a bit of console.log() output to help show what is going on. I have tested it in both Firefox Developer Edition (currently v51.0a2) and Google Chrome.
background.js (no changes to your manifest.json):
'use strict';
//* For testing, open the Browser Console
var isFirefox = window.InstallTrigger?true:false;
try{
if(isFirefox){ //Only do this in Firefox
//Alert is not supported in Firefox. This forces the Browser Console open.
//This abuse of a misfeature works in FF49.0b+, not in FF48
alert('Open the Browser Console.');
}
}catch(e){
//alert throws an error in Firefox versions below 49
console.log('Alert threw an error. Probably Firefox version below 49.');
}
//*
//Firefox gets confused if we try to create notifications too fast (this is a bug in
// Firefox). So, for Firefox, we rate limit showing the notifications.
// The maximum rate possible (minimum delay) is probably OS and CPU speed dependent.
// Thus, you should error on the side of caution and make the delay longer.
// No delay is needed in Chrome.
var notificationRateLimit = isFirefox ? 500:0;//Firefox:Only one notification every 500m
var notificationRateLimitTimeout=-1; //Timeout for notification rate limit
var sentNotifications={};
var notificationsQueue=[];
var notificationIconUrl = chrome.extension.getURL("icons/photo.png");
function logURL(requestDetails) {
//console.log('webRequest.onBeforeRequest URL:' + requestDetails.url);
//NOTE: In Chrome, a webRequest is issued to obtain the icon for the notification.
// If Chrome finds the icon, that webRequest for the icon is only issued twice.
// However, if the icon does not exist, then this sets up an infinite loop which
// will peg one CPU at maximum utilization.
// Thus, you should not notify for the icon URL.
// You should consider excluding from notification all URLs from within your
// own extension.
if(requestDetails.url !== notificationIconUrl ){
notify('webRequest URL: ' + requestDetails.url);
}
//Your Original code in the Question:
//Unconditionally adding an anonymous notifications.onClicked listener
// here would result in multiple lines of 'TEST TEST' ouput for each click
// on a notification. You should add the listener only once.
}
function notify(notifyMessage) {
//Add the message to the notifications queue.
notificationsQueue.push(notifyMessage);
console.log('Notification added to queue. message:' + notifyMessage);
if(notificationsQueue.length == 1){
//If this is the only notification in the queue, send it.
showNotificationQueueWithRateLimit();
}
//If the notificationsQueue has additional entries, they will get
// shown when the current notification has completed being shown.
}
function showNotificationQueueWithRateLimit(){
if(notificationRateLimitTimeout===-1){
//There is no current delay active, so immediately send the notification.
showNextNotification();
}
//If there is a delay active, we don't need to do anything as the notification
// will be sent when it gets processed out of the queue.
}
function showNextNotification() {
notificationRateLimitTimeout=-1; //Indicate that there is no current timeout running.
if(notificationsQueue.length === 0){
return; //Nothing in queue
}
//Indicate that there will be a timeout running.
// Neeed because we set the timeout in the notifications.create callback function.
notificationRateLimitTimeout=-2;
//Get the next notification from the queue
let notifyMessage = notificationsQueue.shift();
console.log('Showing notification message:' + notifyMessage);
//Set our standard options
let options = {
type: "basic",
//If the icon does not exist an error is generated in Chrome, but not Firefox.
// In Chrome a webRequest is generated to fetch the icon. Thus, we need to know
// the iconUrl in the webRequest handler, and not notify for that URL.
iconUrl: notificationIconUrl,
title: "",
message: notifyMessage
};
//If you want multiple notifications shown at the same time, your message ID must be
// unique (at least within your extension).
//Creating a notification with the same ID causes the prior notification to be
// destroyed and the new one created in its place (not just the text being replaced).
//Use the following two lines if you want only one notification at a time. If you are
// actually going to notify on each webRequest (rather than doing so just being a way
// to test), you should probably only have one notification as they will rapedly be
// off the screen for many pages.
//let myId = 'ID123';
//chrome.notifications.create(myId,options,function(id){
//If you want multiple notifications without having to create a unique ID for each one,
// then let the ID be created for you by using the following line:
chrome.notifications.create(options,function(id){
//In this callback the notification has not necessarily actually been shown yet,
// just that the notification ID has been created and the notification is in the
// process of being shown.
console.log('Notification created, id=' + id + ':: message:' + notifyMessage);
logIfError();
//Remember the text so we can get it later
sentNotifications[id] = {
message: notifyMessage
}
//Show the next notification in the FIFO queue after a rate limiting delay
// This is called unconditionally in order to start the delay should another
// notification be queued, even if one is not in the queue now.
notificationRateLimitTimeout = setTimeout(showNextNotification
,notificationRateLimit);
});
}
function logIfError(){
if(chrome.runtime.lastError){
let message =chrome.runtime.lastError.message;
console.log('Error: ' + message);
}
}
chrome.webRequest.onBeforeRequest.addListener(
logURL, {
urls: ["<all_urls>"]
}
);
//Add the notifications.onClicked anonymous listener only once:
// Personally, I consider it better practice to use a named function that
// is defined in the global scope. Doing so prevents inadvertantly adding
// it multiple times. Although, your code should be written such that you
// don't do that anyway.
chrome.notifications.onClicked.addListener(function(id) {
//We can not get the notification text from here, just the ID. Thus, we
// have to use the text which was remembered.
console.log('Clicked notification message text: ', sentNotifications[id].message);
//In Firefox the notification is automatically cleared when it is clicked.
// If you want the same functionality in Chrome, you will need to clear() it
// yourself:
//Always do this instead of only when not in Firefox so that it remains consistent
// Even if Firefox changes to match Chrome.
chrome.notifications.clear(id);
//This is the last place we use the text of the notification, so we delete it
// from sentNotifications so we don't have a memory leak.
delete sentNotifications[id];
});
//Test the notifications directly without the need to have webRequests:
notify('Background.js loaded');
notify('Second notification');
In the process of working on this, I found multiple incompatibilities between Chrome and Firefox. I am in the process of updating MDN to mention the incompatibilities in the documentation on MDN.
I am building AngularJS applications which have common header with links to each of the application:
App1
App2
Each application is running on its own subdomain and when user clicks a link on the header - page redirects to that application.
I have to track user actions with the links, e.g. onClick events with Omniture (but the problem applies to Google Analytics as well). When I add an onClick event that calls a function to send event to Omniture, e.g.:
App1
trackLink() is a function of an AngularJS service, brief implementation:
trackLink: function (eVar8Code) {
s = this.getSVariable(s);
s.eVar8 = eVar8Code;
s.prop28 = s.eVar8;
this.sendOmnitureMessage(s, send, false);
return s;
},
the function executes asynchronously and returns right away. Then standard link's behaviour kicks in: page is redirected to the URL defined in "href" attribute. New page is loaded very quickly (around 70 ms) but AJAX request to Omniture has not been executed: it's all async.
I believe that using events for the links is incorrect approach, one should rather use Query parameters, e.g.:
App1
but it's hard to convince some.
What is a good practise to track events on links?
Change your function to include a short timeout (probably you'd let it return false to suppress default link behaviour, too, and redirect via the location object).
Google Analytics has hit callbacks which are executed after the call to Google was sent, you might want to look if Adobe Analytics has something similar (as this can be used for redirects after the tracking call has been made).
If event tracking and query parameters are interchangeable depends on your use case (they certainly measure different things). However event tracking is a well accepted way for link tracking.
As #Eike Pierstorff suggested - I used capabilities of Adobe Analytics native library to set a delay (200ms) which give the call to Adobe Analytics much better chances to succeed:
in HTML:
App1
in AngularJS service:
sendOmnitureMessageWithDelay: function (s, element, eVar8Code) {
var s = s_gi(s_account); // jshint ignore:line
s.useForcedLinkTracking = true;
s.forcedLinkTrackingTimeout = 200; // Max number of milliseconds to wait for tracking to finish
s.linkTrackVars = 'eVar8,prop28';
s.eVar8 = eVar8Code;
s.prop28 = eVar8Code;
var target = element;
if (!target) {
target = true;
}
s.tl(target, 'o', s.eVar8, null, 'navigate');
this.cleanOmnitureVars();
}
Here, element - is HTML element about.
It works pretty well in 99% of the cases but has issues on the slow and old devices where page loads before call to Adobe has been made. It appears that there is no good solution to this problem and there cannot be guarantee that events would always be recorded in Adobe Analytics (or Google Analytics).
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.
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.
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);