1+ /****************************************************************************************************************************
2+ ****************************
3+ * @file FILE.EXT
4+ *
5+ * @copyright (C) 2022 i-trace.org
6+ *
7+ * This file is part of iTrace Infrastructure http://www.i-trace.org/.
8+ * iTrace Infrastructure is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
9+ * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
10+ * version.
11+ *
12+ * iTrace Infrastructure is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
13+ * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+ * for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License along with iTrace Infrastructure. If not, see
17+ * https://www.gnu.org/licenses/.
18+ ************************************************************************************************************************
19+ ********************************/
20+
21+ // main JavaScript driver for the iTrace-Chrome plugin, all data will be handled here
22+ window . iTraceChrome = {
23+
24+ // this function takes the x and y coordinates from the screen and the browser
25+ // to get the offset, which then returns the translated coordinates based off if the user
26+ // is looking in or outside the viewport
27+ translateCoordinates : function ( x , y ) {
28+ // screen dimensions
29+ var screenX = screen . height ;
30+ var screenY = screen . width ;
31+
32+ var offsetX = screenX - iTraceChrome . browserX ;
33+ var offsetY = screenY - iTraceChrome . browserY ;
34+
35+ if ( x < offsetX || y < offsetY ) {
36+ // user is looking outside of the broswer viewport, most likely at the broswer's shell
37+ return null ;
38+ } else {
39+ // user is looking in the broswer viewport, return the translated coordinates
40+ return { x : x - offsetY , y : y - offsetX } ;
41+ }
42+ } ,
43+
44+ // this function groups files by their name, param data are the files
45+ groupByFilename : function ( data ) {
46+ return data . reduce ( function ( objectsByKeyValue , obj ) {
47+ var value = obj . filename ;
48+ objectsByKeyValue [ value ] = ( objectsByKeyValue [ value ] || [ ] ) . concat ( obj ) ;
49+ return objectsByKeyValue ;
50+ } , { } ) ;
51+ } ,
52+
53+ // this function converts a JSON formatted file to an XML formatted file
54+ json2xml : function ( o , tab ) {
55+ var toXml = function ( v , name , ind ) {
56+ var xml = "" ;
57+ if ( v instanceof Array ) {
58+ for ( var i = 0 , n = v . length ; i < n ; i ++ )
59+ xml += ind + toXml ( v [ i ] , name , ind + "\t" ) + "\n" ;
60+ }
61+ else if ( typeof ( v ) == "object" ) {
62+ var hasChild = false ;
63+ xml += ind + "<" + name ;
64+ for ( var m in v ) {
65+ if ( m . charAt ( 0 ) == "@" )
66+ xml += " " + m . substr ( 1 ) + "=\"" + String ( v [ m ] ) ;
67+ else
68+ hasChild = true ;
69+ }
70+ xml += hasChild ? ">" + "\n" : "/>" ;
71+ if ( hasChild ) {
72+ for ( var m in v ) {
73+ if ( m == "#text" )
74+ xml += v [ m ] + "\n" ;
75+ else if ( m == "#cdata" )
76+ xml += "<![CDATA[" + v [ m ] + "]]>" ;
77+ else if ( m . charAt ( 0 ) != "@" )
78+ xml += toXml ( v [ m ] , m , "\t" ) + "\n" ;
79+ }
80+ xml += ( xml . charAt ( xml . length - 1 ) == "\n" ? ind : "" ) + "</" + name + ">" ;
81+ }
82+ }
83+ else {
84+ xml += ind + "<" + name + ">" + String ( v ) + "</" + name + ">" ;
85+ }
86+ return xml ;
87+ } , xml = "" ;
88+ for ( var m in o )
89+ xml += toXml ( o [ m ] , m , "" ) + "\n" ;
90+ return xml ;
91+ } ,
92+
93+ // this function takes the data from the session and adds it to the iTraceChrome's sessionData attribute
94+ // and stores it in objectStore
95+ printResults : function ( response ) {
96+ chrome . tabs . getSelected ( null , function ( tab ) {
97+ this . currentUrl = tab . url ;
98+ } . bind ( iTraceChrome ) ) ;
99+
100+ // user is looking off screen
101+ if ( response == null ) {
102+ return ;
103+ }
104+ var sessionDataItem = { filename : iTraceChrome . fileLocation , timestamp : response . time , current_timestamp : new Date ( ) . getTime ( ) , x : response . x , y : response . y , area : response . result , line : response . line , word : response . word , tagname : response . tagname , id : response . id , url : iTraceChrome . currentUrl } ;
105+ iTraceChrome . sessionData . push ( sessionDataItem ) ;
106+
107+ if ( iTraceChrome . db != null ) {
108+ var transaction = iTraceChrome . db . transaction ( [ "sessionData" ] , "readwrite" ) ;
109+
110+ var objectStore = transaction . objectStore ( "sessionData" ) ;
111+ objectStore . add ( sessionDataItem )
112+ }
113+ } ,
114+
115+ // writes the sessionData in XML and downloads the file, then resets sessionData and clears objectStore
116+ writeXMLData : function ( ) {
117+ if ( iTraceChrome . websocket != null ) {
118+ iTraceChrome . websocket . close ( ) ;
119+ }
120+
121+ var sessionsData = iTraceChrome . groupByFilename ( iTraceChrome . sessionData ) ;
122+
123+ for ( var file in sessionsData ) {
124+ // call method to parse JSON to xml string, then write to file
125+ var xmlString = iTraceChrome . json2xml ( sessionsData [ file ] ) ;
126+
127+ // create blob with url for chrome.downloads api
128+ var xmlBlob = new Blob ( [ xmlString ] , { type : "text/xml" } ) ;
129+ var url = URL . createObjectURL ( xmlBlob ) ;
130+
131+ var filePath = "chrome_plugin_data.xml" ;
132+ if ( file != "" ) {
133+ filePath = "itrace_chrome_" + file + ".xml" ;
134+ }
135+ // download file
136+ // currently defaults to downloading to the device's download folder
137+ // can be changed to any path in local storage
138+ chrome . downloads . download ( {
139+ url : url ,
140+ filename : filePath
141+ } ) ;
142+ }
143+
144+ iTraceChrome . sessionData = [ ] ;
145+ iTraceChrome . fileLocation = "" ;
146+
147+ if ( iTraceChrome . db ) {
148+ // objectStore is the (temporary) bridge that allows us to get the local data that is locally stored
149+ // in the iTraceChrome object
150+ var objectStore = iTraceChrome . db . transaction ( [ "sessionData" ] , "readwrite" ) . objectStore ( "sessionData" ) ;
151+ objectStore . clear ( ) ;
152+ }
153+ } ,
154+
155+ // if database data and sessionData are empty, this function will load the
156+ // indexed database data
157+ loadIndexedDBData : function ( callback ) {
158+ if ( iTraceChrome . db && iTraceChrome . sessionData . length == 0 ) {
159+ var objectStore = iTraceChrome . db . transaction ( "sessionData" ) . objectStore ( "sessionData" ) ;
160+ objectStore . getAll ( ) . onsuccess = function ( event ) {
161+ iTraceChrome . sessionData = event . target . result ;
162+ callback ( ) ;
163+ }
164+ }
165+ } ,
166+
167+ // this function deals with incoming eyegaze data
168+ webSocketHandler : function ( e ) {
169+ var eyeGazeData = e . data ;
170+
171+ // sets the file's location upon session start
172+ if ( eyeGazeData . substring ( 0 , eyeGazeData . indexOf ( ',' ) ) == 'session_start' ) {
173+ var tmp = eyeGazeData . substring ( eyeGazeData . indexOf ( ',' ) + 1 ) ;
174+ tmp = tmp . substring ( tmp . indexOf ( ',' ) + 1 ) ;
175+ iTraceChrome . fileLocation = tmp . substring ( 0 , tmp . indexOf ( ',' ) ) ;
176+ return ;
177+ }
178+ // if session is no longer active, then set iTraceChrome's active status to false
179+ else if ( eyeGazeData . substring ( 0 , eyeGazeData . indexOf ( ',' ) ) == "session_end" ) {
180+ iTraceChrome . isActive = false ;
181+ }
182+ var timeStampAndCoords = eyeGazeData . substring ( eyeGazeData . indexOf ( ',' ) + 1 ) ;
183+ var timeStamp = timeStampAndCoords . substring ( 0 , timeStampAndCoords . indexOf ( ',' ) ) ;
184+ var coordString = timeStampAndCoords . substring ( timeStampAndCoords . indexOf ( ',' ) + 1 ) ;
185+
186+ var x = coordString . substring ( 0 , coordString . indexOf ( ',' ) ) ;
187+ var y = coordString . substring ( coordString . indexOf ( ',' ) + 1 , coordString . length ) ;
188+
189+ // parse values
190+ x = parseInt ( x ) ;
191+ y = parseInt ( y ) ;
192+
193+ // get translated coordinates
194+ var coords = iTraceChrome . translateCoordinates ( x , y ) ;
195+
196+ if ( ! coords || isNaN ( x ) || isNaN ( y ) ) {
197+ // user is not looking in the html viewport
198+ } else {
199+ // user is looking in the html viewport
200+ // need to check which website the user is looking at
201+ chrome . tabs . query ( { 'active' : true } , function ( tabs ) {
202+ var url = iTraceChrome . tab . url ;
203+ if ( url . includes ( 'stackoverflow.com/questions/' ) ) {
204+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_so_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
205+ }
206+ if ( url . includes ( 'https://bug' ) ) { // NOTE: This include may be incorect, will need to do some more research
207+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_bz_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
208+ }
209+ if ( url . includes ( 'stackoverflow.com/search' ) ) {
210+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_search_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
211+ }
212+ if ( url . includes ( 'google.com' ) ) {
213+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_google_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
214+ }
215+ if ( url . includes ( 'github.com/*/* /issues' ) ) {
216+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_github_issues_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
217+ }
218+ if ( url . includes ( 'github.com/*/*/pulls' ) ) {
219+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_github_prlist_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
220+ }
221+ if ( url . includes ( 'github.com/*/*/pull' ) ) {
222+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_github_pr_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
223+ }
224+ if ( url . includes ( 'github.com' ) && url . includes ( 'pull' ) ) {
225+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_github_pr_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
226+ }
227+ if ( url . includes ( 'github.com/' ) ) {
228+ chrome . tabs . sendMessage ( iTraceChrome . id , { text : 'get_github_dev_profile_coordinate' , x : coords . x , y : coords . y , time : timeStamp , url : url } , iTraceChrome . printResults ) ;
229+ }
230+ } ) ;
231+ }
232+ } ,
233+
234+ // this retrieve's the browser's x coordinate
235+ getBrowserX : function ( result ) {
236+ console . log ( 'browserX' ) ;
237+ console . log ( result ) ;
238+ this . browserX = result [ 0 ] ;
239+ } ,
240+
241+ // this retrieve's the browser's y coordinate
242+ getBrowserY : function ( result ) {
243+ console . log ( 'browserY' ) ;
244+ console . log ( result ) ;
245+ this . browserY = result [ 0 ] ;
246+ } ,
247+
248+ // this function begins a session, which binds the browser's x and y coordinates, initializes and
249+ // binds new websocket, sets status to active then listens for eye gaze data from the server
250+ startSession : function ( tabs , callback ) {
251+ iTraceChrome . tab = tabs [ 0 ] ;
252+ console . log ( 'START SESSION' ) ;
253+
254+ chrome . tabs . executeScript ( {
255+ 'code' : 'window.innerHeight'
256+ } , iTraceChrome . getBrowserX . bind ( iTraceChrome ) ) ;
257+
258+ chrome . tabs . executeScript ( {
259+ 'code' : 'window.innerWidth'
260+ } , iTraceChrome . getBrowserY . bind ( iTraceChrome ) ) ;
261+
262+ iTraceChrome . id = iTraceChrome . tab . id ;
263+
264+ iTraceChrome . websocket = new WebSocket ( 'ws://localhost:7007' ) ;
265+
266+ // listen for eye gaze data coming from the server
267+ iTraceChrome . websocket . onmessage = iTraceChrome . webSocketHandler . bind ( iTraceChrome ) ;
268+ iTraceChrome . isActive = true ;
269+ callback ( iTraceChrome . websocket ) ;
270+ } ,
271+
272+ // this functions opens the iTrace database, upon successfully opening the database it
273+ // calls writeXMLData based on the current state of sessionData that's in objectStore
274+ initializeIndxedDB : function ( ) {
275+ if ( ! window . indexedDB || iTraceChrome . db ) {
276+ return ;
277+ }
278+ var request = window . indexedDB . open ( "iTraceDB" , 3 ) ;
279+ request . onerror = function ( event ) {
280+ console . log ( "Couldn't open indexedDB instance. Won't back up along the way" ) ;
281+ }
282+ request . onupgradeneeded = function ( event ) {
283+ var db = event . target . result ;
284+ db . createObjectStore ( "sessionData" , { keyPath : "timestamp" } ) ;
285+ }
286+ request . onsuccess = function ( event ) {
287+ iTraceChrome . db = event . target . result ;
288+
289+ var objectStore = iTraceChrome . db . transaction ( "sessionData" ) . objectStore ( "sessionData" ) ;
290+ var countRequest = objectStore . count ( ) ;
291+ countRequest . onsuccess = function ( ) {
292+ if ( countRequest . result > 0 ) {
293+ console . log ( "PREVIOUS RESULTS FOUND AND BEING WRITTEN" ) ;
294+
295+ objectStore . getAll ( ) . onsuccess = function ( event ) {
296+ iTraceChrome . sessionData = event . target . result ;
297+ iTraceChrome . writeXMLData ( ) ;
298+ }
299+ }
300+ }
301+ }
302+ } ,
303+
304+ // initializing iTraceChrome
305+ initialize : function ( ) {
306+ if ( iTraceChrome . isInitialized ) {
307+ return ;
308+ }
309+
310+ iTraceChrome . initializeIndxedDB ( ) ;
311+ iTraceChrome . isInitialized = true ;
312+ } ,
313+ isInitialized : false ,
314+ fileLocation : "" ,
315+ isActive : false ,
316+ sessionData : [ ] ,
317+ currentUrl : "" ,
318+ db : null
319+ }
0 commit comments