Capturing mouse actions - javascript

First, some background:
I need to develop a web app that will in background collect all mouse actions by a user (during a visit to a web page), store them in appropriate format in a file, and than have a separate replay app that will be fed with that file, and will produce something like this:
Curves are mouse movements, circles are either clicks or staying stationary.
I have more or less solution for replay app.
I need a solution that captures user mouse actions and saves it in a file on server.
For each user there should be separate file. Format of the file is not predetermined, but following would be reasonable:
<timestamp1> MOVE TO <x1>, <y1>
<timestamp2> MOVE TO <x2>, <y2>
<timestamp3> MOVE TO <x3>, <y3>
<timestamp4> CLICK
<timestamp5> RIGHT-CLICK
<timestamp6> MOVE TO <x6>, <y6>
<timestamp7> MOVE TO <x7>, <y7>
I wonder if you could help me on approach how to design and implement such mouse action capture. All best.

You can easily capture the mouse actions using the click, mousemove, etc. events, in the comments you mentioned you know how to do this, so I'll not detail this.
You can't directly `open' a file on the server, since the code is executed on a completely different machine (ie. the client), so what you'll need to do is send the data from the client to the server every second, or every few seconds.
There are several ways of doing this, here's one way:
Check (& get) a unique userid from document.cookie, or localStorage, if there isn't one, generate one (using Date() and/or Math.random())
Bind events to capture the mouse actions, these events write data (in the format you want) to the Array window.captureMouse.
Send an Ajax request to the server every second (depending on the amount of users, speed of server, you may want to change the interval).
A piece of example code might illustrate the idea better (using jQuery)
userId = fetchOrSetUserId() // Make this function
captureMouse = []
$('#id').on('click', function(e) {
captureMouse.push({
event: 'click',
target: $(this).attr('id'),
})
})
// ... more events ...
sendData = function() {
// You probably need to do locking here, since captureMouse may be changed in an event before it's reset
send = captureMouse
captureMouse = []
jQuery.ajax({
url: '/store-data',
type: 'post',
data: {
userId: userId,
captureMouse: JSON.stringify(send)
},
success: function() {
// Once this request is complete, run it again in a second ... It keeps sending data until the page is closed
setTimeout(sendData, 1000)
}
})
}
// Start sending data
sendData()
On your server, you'll get captureMouse as POST, you will need to decode JSON and append it to a file (which is identified using the userId parameter).

Related

How can I collect data on all BrowserWindows synchronuously in Electron?

I have an app where I spawn several BrowserWindows, with html forms, and I'd like to collect all the data (in order to save it, to be able to spawn them in the same state at a restart) at a press of a button.
At the moment, the only solution I found to do so, is to have each BrowserWindow do ipcRenderer.send every single time any variable changes (not too hard to do with Vuejs 'watchers'), but this seems demanding and inefficient.
I also thought of doing 'executeJavascript' to each window but that does not allow to capture the return value afaik.
I'd just like to be able to send a message from main when a request for saving is made, and wait for the windows to respond before saving all.
EDIT
I found a slightly better way, it looks like this
app.js
// wait for update reponses
ipc.on('update-response', (evt,args) => {
updates[evt.sender.id] = args;
if(Object.keys(updates).length == BrowserWindow.getAllWindows().length) {
// here I do what I need to save my settings, using what is stored in 'updates'
// ...
// and now reset updates for next time
updates = {}
}
});
// now send the requests for updates
BrowserWindow.getAllWindows().map(w => w.send('update'));
renderer.js
ipcRenderer.on('update', () => {
// collect the data
// var data = ...
ipcRenderer.send('update-response', data);
})
and obviously on the renderer side I am listening to these 'update' messages and sending data with 'udpate-response'.
But it seems a bit complicated and so I am sure there is a simpler way to achieve this using the framework.
EDIT 2
I realized that the above does not always work, because for some reason, the evt.sender.id do not match the ids obtained from BrowserWindows.getAllWindows(). I worked around that by sending ids in the request, and having the responder include it. But this is all so much fine for so very little...

Send message from WeChat mini-program to web-view

I'm building WeChat Mini-Program that on one of it's pages has web-view control. For example:
page.wxml
<web-view src="https://..." bindmessage="onWebViewMessage"></web-view>
page.js
const app = getApp();
Page({
onWebViewMessage:function(e) {
console.log(e);
},
onLoad:function() {
}
});
In web-view an HTML page is loaded (index.html), that includes jweixin-1.3.2.js lib from WeChat, for connecting with WeChat API as well as connect to parent Mini-program. Page is empty, no DOM elements, just javascript that will execute when document is loaded.
It has it's javascript something like this:
index.js
document.addEventListener('DOMContentLoaded',function(){
wx.miniProgram.postMessage({data:'test'});
});
I am able to post messages from this document to mini-program without issues. Also can send some mini-program navigation commands such as wx.miniProgram.navigateTo({url:'path/to/page'}); so all seems fine. I can also get callback in Mini-program when web-view has completed loading.
Question:
How can I post message from Mini-program to web-view? For example, to pass a string or an Object to the web-view.
I have been googling for hours and can't seem to find anyone doing it, but I can't believe it's just one-way communication possible.
Any help or idea is appreciated!
I have found an effective way to pass data from mini-program to web-view content, and it seems at this moment in time, this is the only possible way to do it.
Mini-program
1. Base64 module
You will need to be able to convert normal String into Base64 string. Mini-program API has a method for converting byte array into base64 string, but that won't be usable for this purpose. So, create your own module that does that:
File: lib/b64.js
var string2base64 = function(str) {
.... here put your js code for making b64 string ....
return result;
};
module.exports = {
string2base64
};
2. Page with Web-View
In the page that has web-view control, prepare DOM element in wxml file like this:
File: pages/xxx/index.wxml
<web-view src="{{webURL}}" bindload="onWebLoad" binderror="onWebError"></web-view>
Notice that src parameter is now bound to page's webURL property. Whenever page sets value to this property, will automatically be applied to the DOM elemenet.
In file pages/xxx/index.js you will need to add base64 module:
const b64 = require('../../lib/b64.js')
note that require path may vary depending how you have setup your project
and in page's data object, add webURL and webBaseURL properties, like this:
Page({
data: {
webURL:'',
webBaseURL:'https://your/web/app/url',
messageQueue:[],
messageQueueSize:0,
.... other page properties go here ....
},
..... rest of your page code goes here .....
})
Notice that webURL is set to be empty. This means that when page loads, an empty string will be set to DOM object by default.
webBaseURL will explain just in a bit.
messageQueue is an Array that will store pending messages to be sent to web-view.
messageQueueSize is just Array length. Used for better performance, to avoid reading Array.length.
3. Start Message Queue
In onShow callback of the page, set webURL and start interval that will read messageQueue Array every 250ms. You can change the way this is done if you dislike using intervals, this was just simplest way to do theory test.
onShow: function(){
// This will start loading of the content in web-view
this.setData({webURL: this.data.webBaseURL } );
// Sends message from message queue to web-view
let _this = this;
setInterval(function(e) {
if( _this.data.messageQueueSize < 1 ) return;
_this.data.messageQueueSize --;
let msg = _this.data.messageQueue.splice(0,1);
_this.setData({webURL: _this.data.webBaseURL+"#"+msg});
},250);
}
You can see that message is appended to web-view source (url) as a hash.
webBaseURL is used to generate final URL with hash, that is then send to web-view.
4. Add a Message to the Queue
To create a message in message queue, just define following method in your page:
addMessageToQueue: function(obj) {
obj.unique = Math.round(Math.random()*100000);
let msg = b64.string2base64(JSON.stringify(obj));
this.data.messageQueue.push(msg);
this.data.messageQueueSize++;
}
Whenever you call this method, just pass an Object with whatever properties you need it to have, and it will be converted into JSON string, then to base64 string, and finally appended to the message queue.
unique property is added to make generated base64 result always different even if the rest of object properties are the same - I just needed this for the purpose of my project. You can ignore it / remove it if you do not need it.
Since there's interval running and checking on the message queue, all messages added like this will be sent to web-view in the same order they were added to the queue.
Now there's only one thing left - to add hash change listening in the HTML page we have loaded into the web-view:
HTML Web-app
1. Listen to hash change
window.addEventListener("hashchange",function(e){
let messageBase64 = window.location.hash.substr(1);
let json = window.atob( messageBase64 );
let data = JSON.parse(json);
console.log("Received data from mini-program:",data);
});
Tested on Xiaomi Mi8 Pro. I am yet to test on other devices sold in China.
Cheers!

Inform user that message is still being typed

I am using Laravel 5.6.7, Socket.IO and vue.js. I am not using Pusher and redis. Below is my code to send message to user chatting with me one to one.
var url = "http://localhost:6001/apps/My_appId/events?auth_key=My_Key";
var socketId = Echo.socketId();
var request = {
"channel": "private-Send-Message-Channel.2",
"name": "MessengerEvent",
"data": {
"msg": message
},
"socket_id": socketId
};
axios.post(url, JSON.stringify(request)).then((response) => {
//Message Sent
});
I am trying to inform user who is chatting with me that I am still typing. Should I use the same above code which emits xhr on each char type? Is it the only way to inform user that the message typing is still in progress?
Update 1
Is there any better way to post xhr as mentioned above for each key press? I meant if user types 200 chars. will I post xhr 200 times?
or
Do we have an event called whisper and listenForWhisper as shown here https://laravel.com/docs/5.6/broadcasting#client-events ? I am using vue.js and laravel 5.6.7 without pusher and without redis
If you look at the broadcasting documentation you will see two code code snippets which you can use in your Vue.js application.
To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
While the user is typing, you can debounce the whisper method above.
If you don't wish to use another library like lodash, you can implement the debounce by simply wrapping whisper in a timeout. The following method would broadcast the whisper every 300ms:
isTyping() {
let channel = Echo.private('chat');
setTimeout(function() {
channel.whisper('typing', {
name: this.user.name,
typing: true
});
}, 300);
}
The app needs to trigger isTyping() when an onkeydown event occurs in the chat application's input field.
You also need to listen for the whisper once the app is created. The following method will set the typing variable to true for 600ms after the event has been received.
created() {
let _this = this;
Echo.private('chat')
.listenForWhisper('typing', (e) => {
this.user = e.name;
this.typing = e.typing;
// remove is typing indicator after 0.6s
setTimeout(function() {
_this.typing = false
}, 600);
});
},
I am no Laravel expert, but I've faced this problem before.
First, let's define what "typing" means. The simplest way to define it is to say that a user is typing if and only if the input field to send a message is not empty.
This is not perfect, because the user can go away from keyboard in the middle of typing a message then not returning to complete and/or send it, but it is good enough.
More importantly, we now don't need to care about key strokes to know if the user is typing. In fact, "user is typing" now becomes as easy as chat_input_box.length > 0 to represent in code.
This boolean value is what needs to be synced across users/servers, not the act of hitting a key on the keyboard by the user. However, to keep the value up to date, we need to catch input events on chat_input_box and if the boolean value has changed since before this current event has occurred, socket.io should be able send a signal signifying whether the user has stopped or started typing.
On the receiving side, this signal toggles appropriate views to appear or disappear to indicate the state of the app to the user in human terms.
For the shortcoming of a user typing something then leaving, a timeout can be set so that when it is finished the boolean value "is typing" resets to false, while the act of typing something resets the timeout then starts it again automatically.
You don't have to send an xhr request to your app. You can just
broadcast events directly to the chat users without hitting your app.
From the Laravel docs:
Sometimes you may wish to broadcast an event to other connected clients without hitting your Laravel application at all. This can be particularly useful for things like "typing" notifications, where you want to alert users of your application that another user is typing a message on a given screen. To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
Yes you are right, it should not be emitting on every character change instead you could use debouncing to wait for a small time and then fire the function.
I would recommend using lodash library's debounce method. It should be something like this.
Kindly have a look at the documentation: https://lodash.com/docs/4.17.5#debounce
Laravel's Echo also sounds good, as you'll be doing nothing with typing action on the back-end thus just emitting from client to client is better than involving the server.

How to wait for client response with socket.io?

I'm working on an online, turned based game in order to teach myself Node.js and Socket.IO. Some aspects of the game are resolved serverside. At one point during one of these functions, the server may require input from the clients. Is there a way I can "pause" the resolution of the server's function in order to wait for the clients to respond (via a var x = window.prompt)?
Here's an idea of the code I'm working with:
Server:
for (some loop){
if (some condition){
request input via io.sockets.socket(userSocket[i]).emit('requestInput', data)
}
}
Client:
socket.on('requestInput', function (data) {
var input = window.prompt('What is your input regarding ' + data + '?');
//send input back to the server
socket.emit('refresh', input)
});
Any thoughts?
I don't think that is possible.
for (some loop){
if (some condition){
request input via io.sockets.socket(userSocket[i]).emit('requestInput', data)
/* Even if you were able to pause the execution here, there is no way to resume it when client emits the 'refresh' event with user input */
}
}
What you can do instead is emit all 'requestInput' events without pausing and save all responses you will get in socket.on('refresh',function(){}) event in an array, then you can process this array later. I don't know what your exact requirement is but let me know if that works.
Since you are emitting socket.emit('refresh', input) on the client side, you just need to set up a socket event listener on the server side as well. For example:
io.sockets.on('connection', function (socket) {
socket.on('refresh', function (data) {
console.log(data) //input
});
})
I will also point out, so that you don't run into trouble down the line, that indefinite loops are a big nono in node. Nodejs runs on a single thread so you are actually blocking ALL clients as long as your loop is running.

Asynchronous AJAX Calls in orderd manner

Hi firstly sorry for my bad English. I Already searched in SO. but i didn't get the exact answer i needed.
My issue is i need to synch the Ajax request. i know we can use the "asynch : false ".
but this will make browser locked. I have a folder tree(i am using "tafel tree" js) in my web. the tree nodes are generated at run-time. each time
user click a node it will send request to server and add the node to the tree.
but issue is if the page is refreshed by clicking f5 then i need to load the tree structure that i already selected previously.
i implemented it using "asynch : false ". but this will makes browser too slow.
and here what i have
function loadPage() {
/* some other codes are here*/
/* here i call an ajax for get the inside folder list in correct order.(i am usig protoype)*/
new Ajax.Request(ajaxGetInsideFolderIdsUrl,
{
parameters: {branchId: CurrentSelectfolderId},
onSuccess: function(res) {
/* onSuccess i call another function*/
var brs = res.responseText.split(","); // branch ids in correct order.
syncFolder(brs)
}
}
function syncFolder(brs){
for(var i= 0 ; i < brs.length; i ++){
var tempbranch = tree.getBranchById(brs[i].getId());
selectAfterChange( tempbranch)
/*
selectAfterChange function is used for selecting current branch. calling "tafle tree" select() function in side it.
i just created an copy of "select()","_openPopulate()" functions used in "tafle tree" and modified it with "asynch : false ".and now its working fine.
*/
}
}
function selectAfterChange(brs){
brs.chk_select();
/* for selecting the branch (created a copy of current "select()" function used in "tafle tree" js
and modified it with "asynch : false "and now its working fine.) */
showList();// for listing items in side that folder (in another Ajax page).
}
My problem is if a user opened a long branch.
And then refresh the page it will take too much time to load because of synch Ajax call.
Taking too much time is not an big issue to me. but the browser is get locked until all the request executed.
is there any other way to do this.
I'm not familiar with the tree library you're using, but in general, the way you'd solve this is to store the currently expanded path(s) in the browser (local storage, or cookies, or wherever), and when you refresh the page, load just the nodes that are visible.
Let's say that the user is currently looking at path one/two/three/four. You save that path somewhere, and then when the page loads again, you create a queue of paths to request from the back end, by splitting the path, and appending the components of the path one by one:
["one", "one/two", "one/two/three"]
You then send the AJAX request for "one", and when you get the result back, update that node in the tree, and send a request for "one/two". When that request returns, update the tree, and send the request for "one/two/three".
Depending on your design, you can then start filling in the rest of the tree with more async requests...

Categories

Resources