/** * Created by PJR on 8/5/2015. * Updated Aug 2019 for 0.4 * Minor adjustment to data reporting, Mar 2019 to handle non-list data uploads * Added upload failure recovery (aug 2019) */ // https://www.reberlab.org/static/SISL.html?group=de945007aec0184d&assignmentId=123RVWYBAZW00EXAMPLE456RVWYBAZW00EXAMPLE&hitId=123RVWYBAZW00EXAMPLE&turkSubmitTo=https://www.mturk.com/&workerId=AZ3456EXAMPLE var server_debug = true; var ServerHelper = { server_url: '', image_url: '', xmlhttp: new XMLHttpRequest(), groupToken: '', sessionToken: "", config_file: "", consent_string: "", // this is the original JSON string from Empirical consent_form: {}, // which gets parsed into an object workerId: "", fatal_error: false, error: "", response_log: "", status: "", status_time: "", status_since: 0.0, demo_mode: false, mturk: false, mturk_submit: '', mturk_info: '', start_requested: false, start_received: false, data_logged: false, upload_requested: false, upload_in_progress: false, upload_queue: [], upload_connection_log: '', upload_failed: false, empirical_start: function(url) { var params={}; var q=url.split('?'); var u=window.location; var prompt_type='none'; if (u.port=='' || u.port==80) host=u.hostname; else host=u.hostname+':'+u.port; this.server_url = u.protocol + '//' + host + '/'; this.image_url = u.protocol + '//' + host + '/images/'; if (q.length<2) return(params); q = q[1].split('&'); for(var i=0;i < q.length;i++) { var t=q[i].split('='); if(t.length==1) params[t[0]]='None'; else if(t.length==2) params[t[0]]=t[1]; else params[t[0]]= t.slice(1); } if (params.hasOwnProperty('group')) { this.groupToken = params['group']; } else { this.error="No group token in URL"; this.fatal_error=true; } if (params.hasOwnProperty('workerId')) this.workerId = params['workerId']; else if (params.hasOwnProperty('workerid')) this.workerId = params['workerid']; else if (params.hasOwnProperty('name')) this.workerId = params['name']; else this.workerId=''; if (params.hasOwnProperty( 'prompt')) prompt_type=params['prompt'].toLowerCase(); if (this.workerId=='demo' || params.hasOwnProperty('demo')) this.demo_mode=true; else if (this.workerId!='' && prompt_type=='mturk') { ServerHelper.mturk=true; } else if (this.workerId=='' && prompt_type!='None' && prompt_type!='none') { // prompt for name/id var prompt_string= "Please enter your User ID:"; if (prompt_type=='mturk') { prompt_string= "Please enter your mTurk Worker ID:"; ServerHelper.mturk=true; } else if (prompt_type=='sona') prompt_string= "Please enter your SONA ID number:"; var typed_name = prompt(prompt_string); var name_ok = /^[A-Za-z0-9_]+$/i.test(typed_name); while (!name_ok || typed_name==null || typed_name.length<1) { typed_name = prompt("Please enter your id (only numbers, letters or underscore):"); name_ok = /^[A-Za-z0-9_]+$/i.test(typed_name); } this.workerId=typed_name; //response_log.push("SubjectID: " + this.workerId); } if (params.hasOwnProperty('assignmentId')) { // this is an mturk session this.mturk=true; if (params['assignmentId']=='ASSIGNMENT_ID_NOT_AVAILABLE') { this.demo_mode=true; } else { var mturk_info="assignmentId=" + params['assignmentId'] + "\n"; if (params.hasOwnProperty('hitId')) mturk_info+="hitId=" + params['hitId'] + "\n"; mturk_info+="workerId=" + this.workerId + "\n"; if (params.hasOwnProperty('turkSubmitTo')) mturk_info+="turkSubmitTo=" + params['turkSubmitTo'] + "\n"; this.mturk_submit=params['turkSubmitTo']; this.mturk_info=mturk_info; } } return(params); }, start_request: function() { if (this.start_requested) { if (server_debug) console.log("Multiple calls to start_request"); return; } if (this.demo_mode) { start_request_url = this.server_url + 'exp/start/' + this.groupToken + '/demo'; } else if (this.workerId=='') { var start_request_url = this.server_url + 'exp/start/' + this.groupToken; } else { start_request_url = this.server_url + 'exp/start/' + this.groupToken + '/' + this.workerId; } this.xmlhttp.addEventListener('load', this.start_receive); this.xmlhttp.open("GET", start_request_url, true); this.xmlhttp.send(); this.start_requested = true; }, start_receive: function() { if (this.start_received || this.config_received) { if (server_debug) console.log("Multiple calls to start_receive"); return; } if (ServerHelper.xmlhttp.readyState == 4) { ServerHelper.start_received = true; if (ServerHelper.xmlhttp.status == 200) { var response = ServerHelper.xmlhttp.responseText; parser = new DOMParser(); xmlDoc = parser.parseFromString(response,"text/xml"); // the response is an XML object with the session token, workerid, config file and consent form ServerHelper.sessionToken = xmlDoc.getElementsByTagNameNS("https://www.reberlab.org/","session")[0].childNodes[0].nodeValue; ServerHelper.workerId = xmlDoc.getElementsByTagNameNS("https://www.reberlab.org/","workerid")[0].childNodes[0].nodeValue; ServerHelper.config_file = xmlDoc.getElementsByTagNameNS("https://www.reberlab.org/","config")[0].childNodes[0].nodeValue; ServerHelper.consent_string = xmlDoc.getElementsByTagNameNS("https://www.reberlab.org/","consent")[0].childNodes[0].nodeValue; // what happens if the XML isn't parsed properly? } else { ServerHelper.fatal_error=true; ServerHelper.error = ServerHelper.xmlhttp.statusText; } } }, request_status: function () { var url = this.server_url + 'exp/status/' + this.sessionToken + '/' + this.workerId; this.xmlhttp = new XMLHttpRequest(); this.xmlhttp.addEventListener('load', this.get_status); this.xmlhttp.open("GET", url, true); this.xmlhttp.send(); }, get_status: function () { if (ServerHelper.xmlhttp.readyState == 4) { ServerHelper.status_received = true; if (ServerHelper.xmlhttp.status == 200) { //ServerHelper.status = ServerHelper.xmlhttp.responseText; if (ServerHelper.xmlhttp.responseText == 'None') { ServerHelper.status='None'; ServerHelper.status_time='None'; ServerHelper.status_since='None'; } else { var response = ServerHelper.xmlhttp.responseText; parser = new DOMParser(); xmlDoc = parser.parseFromString(response,"text/xml"); ServerHelper.status=xmlDoc.getElementsByTagNameNS("https://www.reberlab.org/","status")[0].childNodes[0].nodeValue; ServerHelper.status_time=xmlDoc.getElementsByTagNameNS("https://www.reberlab.org/","uploaddate")[0].childNodes[0].nodeValue; ServerHelper.status_since=Number.parseFloat(xmlDoc.getElementsByTagNameNS("https://www.reberlab.org/","timesince")[0].childNodes[0].nodeValue); } } else { ServerHelper.error = ServerHelper.xmlhttp.statusText; } } }, upload_data: function (event_type, response_log) { // start the upload process by requesting the form to get the csrf token // stringify response log var data = ""; if ((typeof response_log)=='string') data=response_log; else { for (var i = 0; i < response_log.length; i++) data = data + response_log[i] + "\n"; } if (this.upload_in_progress) { // queue the next upload file this.upload_queue.push([event_type, data]); if (server_debug) console.log('queued a ' + event_type); if (!this.upload_failed) return; // if upload_failed is true, retry the upload below } this.event_type = event_type; this.response_log = data; var url = this.server_url + 'exp/report/' + this.sessionToken; this.xmlhttp = new XMLHttpRequest(); this.xmlhttp.addEventListener('load', this.upload_ready); this.xmlhttp.open("GET", url, true); this.xmlhttp.timeout = 10000; // 10s timeout this.xmlhttp.ontimeout = this.upload_error; this.xmlhttp.onerror = this.upload_error; this.xmlhttp.send(); this.upload_in_progress = true; }, upload_from_queue: function () { if (this.upload_queue.length == 0) { this.upload_in_progress = false; return; } var next_upload = this.upload_queue.pop(); this.upload_data(next_upload[0],next_upload[1]); return; }, upload_error: function() { console.log("Upload error"); ServerHelper.upload_failed=true; }, upload_ready: function () { // or just xmlhttp post? if (ServerHelper.xmlhttp.readyState != 4) { if (server_debug) console.log("Server state " + ServerHelper.xmlhttp.readyState.toString()); ServerHelper.upload_connection_log+='.'; // adds '.' whenever this is called but server wasn't ready return; } else if (ServerHelper.xmlhttp.status != 200) { // form request didn't work... should recover if (server_debug) console.log("upload error"); // store a record of response errors ServerHelper.upload_connection_log+='status_error_'+ServerHelper.xmlhttp.status.toString()+';'; terminate(ServerHelper.xmlhttp.statusText); // does this end the program or fail silently? } // find csrf token //console.log("form response "+ServerHelper.xmlhttp.responseText); var token_loc = ServerHelper.xmlhttp.responseText.search("csrfmiddlewaretoken"); if (token_loc < 0) { if (server_debug) console.log(ServerHelper.xmlhttp.responseText); // this should be logged as well ServerHelper.upload_connection_log+='csrf_error;' } else { var value = ServerHelper.xmlhttp.responseText.slice(token_loc).match(/value="([^"]*)/); var csrf_token=''; if (value==null) { console.log(ServerHelper.xmlhttp.responseText); value = ServerHelper.xmlhttp.responseText.slice(token_loc).match(/value='([^']*)'/); csrf_token = value[1]; } else csrf_token = value[1]; } var formData = new FormData(); formData.append("csrfmiddlewaretoken", csrf_token); formData.append("eventType", ServerHelper.event_type); formData.append("sessionToken", ServerHelper.sessionToken); formData.append("appName", window.location.pathname); formData.append("workerId", ServerHelper.workerId); formData.append("dataLog", ServerHelper.response_log); ServerHelper.xmlhttp = new XMLHttpRequest(); ServerHelper.xmlhttp.open("POST", ServerHelper.server_url + 'exp/report/' + ServerHelper.sessionToken); ServerHelper.xmlhttp.send(formData); if (server_debug) console.log("data sent " + ServerHelper.event_type); // log successes ServerHelper.upload_connection_log+='data_upload_sent('+ServerHelper.response_log.length.toString()+'_bytes);'; // report on upload errors here... // if there is a queue, start the next upload ServerHelper.upload_failed=false; ServerHelper.upload_in_progress=false; if (ServerHelper.upload_queue != []) ServerHelper.upload_from_queue(); else ServerHelper.upload_in_progress = false; }, // Create form to submit the final data to mturk upload_to_mturk: function(summary) { if (ServerHelper.mturk_submit=='') return; // External survey style mturk assignment, no form used var url = decodeURIComponent(ServerHelper.mturk_submit) + '/mturk/externalSubmit'; // this is supposed to be constructed from URL params... // this was assembled initially but not uploaded until the end this.upload_data('private',this.mturk_info); var form_holder = document.getElementById("formholder"); if(server_debug) console.log("setting form"); var mturk_response=summary+';sesssionToken='+this.sessionToken+';groupToken='+this.groupToken+';connection_log='+ServerHelper.upload_connection_log; var formString = "
Press Submit to finish this experiment
"; if(server_debug) console.log(formString); form_holder.innerHTML = formString; }, };