I've got 3 functions, checkusername(username), checkemail(email), checkpassword(password). The API each one checks against has a rate limit and there has to be a delay of 15 milliseconds between each call. I have tried:
setTimeout(checkusername(username), 1500);
setTimeout(checkemail(email), 1500);
setTimeout(checkpassword(password), 1500);
That doesn't work. I've tried various other ways like increasing the delay by 1500 using a variable:
var delay = 1500;
checkusername(username);
if(delay == 1500) {
delay = delay + 1500;
checkemail(email);
}
else if(delay == 3000) {
delay = delay + 1500;
checkpassword(password);
}
console.log('Checks done!');
None of it works, still getting the code 429 (rate limit exceeded) come up in the console as per from HaveIbeenpwned API.
Either an answer using Javascript or jQuery or I could possibly try it in PHP if there is a solution that way?
By the way each function uses jQuery $.ajax({}) call to the API.
EDIT:
function checkusername(username) {
$.ajax({
url: 'https://haveibeenpwned.com/api/v2/breachedaccount/'+username+'?includeUnverified=true',
type: 'GET',
dataType: 'json',
success: function(data) {
var html = '<h4>Your username was found in the following hacked site breaches:</h4>';
for(i=0;i<data.length;i++) {
var breachName = data[i].Title;
var breachDesc = data[i].Description
html += '<h3><span class="label label-danger">'+breachName+'</span></h3>';
html += '<p>'+breachDesc+'</p>';
html += '<br>';
}
$('#results').append(html);
}
});
}
First, here's a helper function that will return a Promise that will resolve in 15 milliseconds.
function delay15 () {
return new Promise(function(resolve) { setTimeout(resolve, 15) })
}
You'd need to write your checkusername, checkemail, & checkpassword functions to return Promises when the request has completed.
Example checkusername function (note the added return statement):
function checkusername(username) {
return $.ajax({
url: 'https://haveibeenpwned.com/api/v2/breachedaccount/'+username+'?includeUnverified=true',
type: 'GET',
dataType: 'json',
success: function(data) {
var html = '<h4>Your username was found in the following hacked site breaches:</h4>';
for(i=0;i<data.length;i++) {
var breachName = data[i].Title;
var breachDesc = data[i].Description
html += '<h3><span class="label label-danger">'+breachName+'</span></h3>';
html += '<p>'+breachDesc+'</p>';
html += '<br>';
}
$('#results').append(html);
}
});
}
Then you'd write code something like this:
checkusername(username)
.then(delay15)
.then(function() { return checkemail(email) })
.then(delay15)
.then(function() { return checkpassword(password) })
Here's the same code in ES2015, which is shorter, though not as well-supported in browsers:
function delay15 () {
return new Promise(resolve => setTimeout(resolve, 15))
}
checkusername(username)
.then(delay15)
.then(() => checkemail(email))
.then(delay15)
.then(() => checkpassword(password))
your setTimeout method is triggering all 3 requests essentially simultaneously, but 1.5 seconds after the setTimeout is invoked. try chaining your requests as promises or callbacks so you're not sending them all at the same time.
EDIT:
here's your example:
function checkUsername(u) {
return new Promise(function(resolve, reject) {
//check username
if (success) {return resolve();}
else {return reject();}
}
}
checkUsername('username').then(function() {checkNextThing()});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
You probably have this sorted by now but on the off-chance that you do not then perhaps the following mighht be of use ~ seems to finally work OK! Rather than using jQuery ( I don't use it ) or the traditional XMLHttpRequest this relies upon the newer Fetch api which is based around the notion of Promises
<!doctype html>
<html>
<head>
<title>Have I been pwned?</title>
<script>
var flags={
capture:false,
passive:true,
once:false
};
/* Results from each request will be stored here */
var data={};
/* Whether or not to return FULL data as response */
var truncate=false;
/* Whether or not to include unverified results */
var verified=false;
/* A randomly chosen email address & password for testing */
var username='dave#gmail.com';
var email='andy#yahoo.com';
var password='knickers';
/* No idea who dave#gmail.com or andy#yahoo.com is btw */
/* Request configuration */
var config={
'method':'get',
'mode':'cors'
};
/* Error callback function */
var evtError=function(err){
console.info('oops: %s',err.message);
};
/* Time to wait */
const t=1500;
/* Promise to wait specified time */
const wait = ms => new Promise( resolve => setTimeout( resolve, ms ) );
function create( t, a, p ) {
try{
var el = ( typeof( t )=='undefined' || t==null ) ? document.createElement( 'div' ) : document.createElement( t );
for( var x in a ) if( a.hasOwnProperty( x ) && x!=='innerHTML' ) el.setAttribute( x, a[ x ] );
if( a.hasOwnProperty('innerHTML') ) el.innerHTML=a.innerHTML;
if( p!=null ) typeof( p )=='object' ? p.appendChild( el ) : document.getElementById( p ).appendChild( el );
return el;
}catch(err){
console.warn('createNode: %s, %o, %o',t,a,p);
}
}
function process( data ){
if( typeof( data )=='object' ){
/* use the data */
console.info(data)
var div=document.getElementById('results');
var _username=data.username;
var _email=data.email;
var _pwd=data.password;
/* Process username pwnage */
create('h1',{innerHTML:'Username pwnage for: '+username },div );
for( var n in _username ){
if( typeof( _username[ n ] ) =='object' ){
var keys=Object.keys( _username[ n ] );
var values=Object.values( _username[ n ] );
keys.forEach(function(e,i,a){
create(null,{innerHTML:e+': '+values[i]},div);
});
}
}
/* Process email pwnage */
create('h1',{innerHTML:'Email pwnage for: '+email },div );
for( var n in _email ){
if( typeof( _email[ n ] ) =='object' ){
var keys=Object.keys( _email[ n ] );
var values=Object.values( _email[ n ] );
keys.forEach(function(e,i,a){
create(null,{innerHTML:e+': '+values[i]},div);
});
}
}
/* Finally - password pwnage */
create('h1',{innerHTML:'Password pwnage for: '+password },div );
create(null,{innerHTML:_pwd},div);
}
}
function pwned( event ){
var urls={
username:'https://haveibeenpwned.com/api/v2/breachedaccount/'+encodeURIComponent( username )+'?truncateResponse='+truncate+'&includeUnverified='+verified,
email:'https://haveibeenpwned.com/api/v2/breachedaccount/'+encodeURIComponent( email )+'?truncateResponse='+truncate+'&includeUnverified='+verified,
password:'https://haveibeenpwned.com/api/v2/pwnedpassword/'
};
/* Get the first url using "fetch" rather than XMLHttpRequest */
fetch( urls.username, config ).then( function( res ){
if( res.ok ) return res.json();
if( res.status==404 )return 404;
throw new Error('Failed to check username');
}).then( function( json ){
/* Add the response data to output object */
data.username=json;
}).then( function(){
/* wait for pre-determined time */
return wait( t );
}).then( function(){
/* Get the second url */
fetch( urls.email, config ).then(function( res ){
if( res.ok ) return res.json();
if( res.status==404 )return 404;
throw new Error('Failed to check email');
}).then( function( json ){
/* Add new response data to output object */
data.email=json;
}).then( function(){
/* Loiter, with intent, for a while... twiddle the thumbs etc */
return wait( t );
}).then( function(){
/* Finally get the last url - using POST as I found I was having issues with GET for some reason */
var headers=new Headers();
headers.append('Content-Type','application/x-www-form-urlencoded; charset=UTF-8')
var config={
'mode':'cors',
'method':'post',
'body':'Password='+encodeURIComponent( password ),
'headers':headers
};
fetch( urls.password, config ).then( function( res ){
if( res.ok ) return res.json();
if( res.status==404 )return 404;
throw new Error('Failed to check password');
}).then( function(status){
/* And store the response */
data.password=status==200 ? 'pwned' : 'ok';
return status;
}).then(function(status){
return data;
}).then( function( data ){
/* play with the data */
process.call( this, data );
}).catch( evtError );
}).catch( evtError );
}).catch( evtError );
}
document.addEventListener('DOMContentLoaded',function(){
var bttn=document.querySelector('input[type="button"][name="pwnage"]');
bttn.onclick=pwned;
},flags);
</script>
</head>
<body>
<form method='post'>
<input type='button' name='pwnage' value='Check pwnage' />
<div id='results'></div>
</form>
</body>
</html>
I found that it was possible to lower the timeout period to 1000ms on occasion but seemed a little flaky. The implementation of the method chaining could probably be improved upon but it was my first attempt at a really complicated usage of fetch
Doing further testing lead me to try some of my own email addresses as I'd not been on have I been pwned for quite a while - not a happy bunny after finding a couple had been pwned :(
Related
I have a function, which will either return a cached template or if the template has not been cached - it will load it via AJAX and then return it. Here's what I've got:
var getTpl = function( name ) {
var cached = cache.get( 'templates' ) || {};
if( cached.hasOwnProperty( name ) ) {
console.log( 'template ' + name + '.mustache found in cache' );
return cached[ name ];
}
else {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
var tpl;
$.ajax( {
url: path.templates + '/' + name + '.mustache',
async: false,
success: function( data ) {
tpl = data;
var cached = store.get( 'miniTemplates' ) || {};
var newTemplate = {};
newTemplate[ name ] = data;
if( ! cached.hasOwnProperty( name ) ) cache.set( 'templates', _.extend( cached, newTemplate ) )
},
error: function() { tpl = false; }
} );
return tpl;
}
}
This works fine. However, Chrome is complaining about:
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/.
Therefore I wanted to switch to using $.deferred, but I can't wrap my head around it. How can I re-write the function above, so calling getTpl would always return a template (either form the cache or directly from the AJAX request)?
You can use promise/deferred concept to achieve your needs
var getTpl = function( name ) {
var promise;
var cached = cache.get( 'templates' ) || {};
if( cached.hasOwnProperty( name ) ) {
console.log( 'template ' + name + '.mustache found in cache' );
var df = new $.Deferred();
df.resolve(cached[ name ]);
promise = df.promise();
} else {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
promise = $.ajax({
url: path.templates + '/' + name + '.mustache'
}).then(function(data) {
tpl = data;
var cached = store.get( 'miniTemplates' ) || {};
var newTemplate = {};
newTemplate[ name ] = data;
if( ! cached.hasOwnProperty( name ) ) cache.set( 'templates', _.extend( cached, newTemplate ) )
return tpl;
});
}
return promise;
}
Then, call your method like this:
getTpl('xyz')
.then(function(template) {
// you have the template, either from cache or fetched via ajax
})
.fail(function(err) {
console.log(err);
});
Since you appear to appear to already be using underscore/lodash, you can make use of memoization rather than maintaining your own cache.
The beauty of promises is that you can access them again and again and they will always produce the same value:
var getTpl = _.memoize(function( name ) {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
return $.ajax({
url: path.templates + '/' + name + '.mustache'
});
});
Yes, it really is that simple.
Then you can just use it like any other promise:
getTpl('myTemplate').then(function (template) {
// use template
}, function (error) {
console.log('Could not retrieve template.', error);
});
I'm trying to send a several requests from a single instance of XMLHttpRequest object, like this:
GC3D.Application.prototype.setMapTiles = function() {
var ajaxInstance = new GC3D.Ajax();
for ( var i = 0, j = 0; i < 10; i++ ) {
var urlFinal = this.prepareRequestForTileDownload( i, j );
ajaxInstance.sendRequest({
id: { i: i, j: j },
HttpMethod: 'GET',
UrlEndpoint: urlFinal
}).then( function( item ) {
//...
});
}
};
It doesn't work and sends only the one request. BUT! If to change source code to the next:
for ( var i = 0, j = 0; i < 10; i++ ) {
var ajaxInstance = new GC3D.Ajax();
...
It begins to send so many requests as the total number of for loop iterations and all is working perfectly. I prefer to get known some features. As in C# development in my past I never create a new instance of some TCP socket in loop, if I want to make it asynchronously - I create a single instance with delegates to async functions and if there is a similar situation in C# project like in JavaScript code here in the question's content, because it's doesn't produce new objects in for loop, it takes less memory and as for architecture it's represented as more clean and good solution.
GC3D.Ajax is defined as the next prototype:
GC3D.Ajax = function() {
this.httpRequest = undefined;
this.listExceptions = undefined;
this.init();
};
GC3D.Ajax.prototype.init = function() {
this.listExceptions = [];
if ( window.XMLHttpRequest ) this.httpRequest = new XMLHttpRequest();
else if ( window.ActiveXObject ) {
try {
this.httpRequest = new ActiveXObject( 'Microsoft.XMLHTTP' );
}
catch ( exception ) {
this.listExceptions.push( exception );
try {
this.httpRequest = new ActiveXObject( 'Msxml2.XMLHTTP' );
}
catch ( exception ) {
this.listExceptions.push( exception );
try {
this.httpRequest = new ActiveXObject( 'Microsoft.XMLHTTP' );
}
catch ( exception ) {
this.listExceptions.push( exception );
}
}
}
}
if ( !this.httpRequest ) {
console.error( 'Can\'t create a HTTP Request instance for AJAX! Possible problems:' );
console.error( this.listExceptions );
}
};
GC3D.Ajax.prototype.sendRequest = function( properties ) {
var defer = new GC3D.Defer();
if ( this.httpRequest !== undefined ) {
this.httpRequest.onreadystatechange = function() {
if ( this.httpRequest.readyState === 4 && this.httpRequest.status === 200 ) {
var objectOutput = {};
objectOutput.id = properties.id;
objectOutput.content = this.httpRequest.responseText;
defer.resolve( objectOutput );
}
else {
var message = 'There was a problem with the request in GC3D.Ajax.';
defer.reject( message );
}
}.bind( this );
this.httpRequest.open( properties.HttpMethod, properties.UrlEndpoint );
this.httpRequest.send();
}
else console.error( 'HTTP Request instance isn\'t defined!' );
return defer.promise;
};
GC3D.Ajax.prototype.get = function() {
return this.httpRequest;
};
The XMLHttpRequest API is very well defined and leaves no doubt about this behaviour.
The definition of the open() method, step 13, states that the method should:
Terminate the request.
http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
Opening a debugger in the browser and having a look at the network traffic reveals that, in fact, several requests are being send asynchronously, with each but the last request being canceled by the next one.
In this case, the open() method is being invoked repeatedly on the same instance of an XMLHttpRequest inside the sendRequest method of ajaxInstance.
I am learning about localStorage and it sounds like each browser gives a domain 5MB.
I wrote this code to cache the data returned from an ajax call and it works. But how do I test to see if localStorage is full? If there is no localStorage space available I imagine that the ajax request should be made again.
Here's my code:
if ( localStorage && localStorage.getItem('myGithub') ) {
console.log('if statement');
console.log( JSON.parse( localStorage.getItem( 'myGithub') ) );
render( JSON.parse( localStorage.getItem( 'myGithub') ) );
}
else {
console.log('else statment');
$.ajax({
url : 'https://api.github.com/users/xxxxxxxxx',
dataType : 'json',
success : function (data) {
if ( localStorage ) {
localStorage.setItem( 'myGithub', JSON.stringify(data) );
}
console.log(data);
render(data);
}
});
}
//Render method for printing the results to the <body> element.
//Returns html from the ajax call or from localStorage.
function render (myObjx) {
var results = '';
for (var prop in myObjx) {
results += '<p>data.' + prop + ' = ' + myObjx[prop] + '</p>';
}
var printData = $('body').html(results);
return printData;
};
You can use the below approach. You can change it as per your requirement.
function checkAvailable(){
var test = 'test';
try {
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch(e) {
return false;
}
}
// And you call below to check the availablity
if(checkAvailable() === true){
// available
}else{
// unavailable
}
I'm an old 'C' programmer and with Javascript always struggle populating my data following ajax calls; i.e. I always resort to using global references. I'd rather be able to pass in the objects I need to update. Here's one example of what I do now - 'app' is the global (I'd have used a pointer in C :))
treeMapp.login = function ( dialog_div, form_div, server_call ) {
// validate the fields
if ( 0 ) {
}
else {
// send to server & parse JSON response (single line)
var jqxhr =
$.getJSON( server_call,
$( "#" + form_div ).serialize() )
.done( function( data, status ) {
if( status == 'success' ) {
// hack!?
app.user.username = data.username;
app.user.organisation = data.organisation;
app.user.loggedIn = true;
//close the dialog
$( '#' + dialog_div ).dialog('close');
}
else {
// login failed
alert( "login failed!" );
}
})
.fail( function() {
alert( "login: server error" );
}); // end var jqxhr =
} // end else (field validation ok)
}; // end treeMapp.login()
What's the best way of updating passed in parameters?
thanks
mini
You can pass app as an argument to your treeMapp.login function, then within the scope of it it would be local.
treeMapp.login = function ( dialog_div, form_div, server_call, app )
You could assign the data object returned by your jQuery result to app.user, thus avoiding the need for element by element assignment.
i.e. app.user = data
However, normally you ensure the global object can self initialise through a method, or you pass a reference to the global object to the method so it can initialise. Directly using assignment to global variables is (with a few exceptions) poor programming in Javascript as in any other language
UPDATE: the following shows an amalgamation of the answers...
treeMapp.login = function ( dialog_div, form_div, server_call, theapp ) {
var app = theapp; // may be needed for scope issue..
// validate the fields
if ( 0 ) {
}
else {
// send to server & parse JSON response (single line)
var jqxhr =
$.getJSON( server_call,
$( "#" + form_div ).serialize() )
.done( function( data, status ) {
if( status == 'success' ) {
// not a hack
$.extend(this.app.user, data, { loggedIn: true })
//close the dialog
$( '#' + dialog_div ).dialog('close');
}
else {
// login failed
alert( "login failed!" );
}
})
.fail( function() {
alert( "login: server error" );
}); // end var jqxhr =
} // end else (field validation ok)
}; // end treeMapp.login()
I have the following code, which throws a InvalidStateError in Firefox (when calling objectStore).
The message is A mutation operation was attempted on a database that did not allow mutations..
Strangely, when I place the creation of the transaction inside the getBlob callback it seems to work.
function saveFile( fileEntry ) {
var transaction = this;
transaction.onerror = function( event ) {
console.log( event );
};
fileEntry.getBlob( "", function( blob ) {
var store = transaction.objectStore( STORE_NAME );
try {
var request = store.put( blob, fileEntry.getFullname() );
request.onsuccess = function( event ) {
console.log( "file: " + fileEntry.getFullname() );
++(progressBar[0].value);
};
request.onerror = function( event ) {
console.log( "error in file " + event );
++(progressBar[0].value);
};
} catch( ex ) {
console.log("getBlob " + ex );
}
}, function() {
}, true );
}
function saveRecursive( dirEntry, missingFiles ) {
var db = this;
var transaction = db.transaction( [STORE_NAME],
MODES.READ_WRITE );
for( var i in dirEntry.children ) {
var entry = dirEntry.children[i];
if( entry.directory ) {
createDirectory( dirEntry, entry );
saveRecursive.call( db, entry, missingFiles );
continue;
}
var index = missingFiles.indexOf( entry.getFullname() );
if( index == -1 )
continue;
// Still missing - add
missingFiles.splice( index, 1 );
saveFile.call( transaction, entry );
}
}
Could someone explain to me, why this is not working?
If getBlob is async function, this will not work:
fileEntry.getBlob( "", function( blob ) {
var store = transaction.objectStore( STORE_NAME );
because transaction is already committed, when you get the blob.
Not sure if blob supported in indexeddb yet.