algoliasearch-rails 1.11.17 → 1.11.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +4 -0
  3. data/README.md +48 -6
  4. data/VERSION +1 -1
  5. data/algoliasearch-rails.gemspec +22 -6
  6. data/vendor/assets/javascripts/algolia/algoliasearch.angular.js +1446 -1378
  7. data/vendor/assets/javascripts/algolia/algoliasearch.angular.min.js +2 -2
  8. data/vendor/assets/javascripts/algolia/algoliasearch.jquery.js +1446 -1378
  9. data/vendor/assets/javascripts/algolia/algoliasearch.jquery.min.js +2 -2
  10. data/vendor/assets/javascripts/algolia/algoliasearch.js +1446 -1378
  11. data/vendor/assets/javascripts/algolia/algoliasearch.min.js +2 -2
  12. data/vendor/assets/javascripts/algolia/bloodhound.min.js +7 -0
  13. data/vendor/assets/javascripts/algolia/typeahead.jquery.min.js +7 -0
  14. data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.js +2667 -0
  15. data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.min.js +7 -0
  16. data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.js +2667 -0
  17. data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.min.js +7 -0
  18. data/vendor/assets/javascripts/algolia/v2/algoliasearch.js +2653 -0
  19. data/vendor/assets/javascripts/algolia/v2/algoliasearch.min.js +7 -0
  20. data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.js +1717 -0
  21. data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.min.js +37 -0
  22. data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.js +1702 -0
  23. data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.min.js +37 -0
  24. data/vendor/assets/javascripts/algolia/v3/algoliasearch.js +2732 -0
  25. data/vendor/assets/javascripts/algolia/v3/algoliasearch.min.js +50 -0
  26. metadata +36 -22
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * algoliasearch 2.9.4
3
+ * https://github.com/algolia/algoliasearch-client-js
4
+ * Copyright 2014 Algolia SAS; Licensed MIT
5
+ */
6
+
7
+ function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f<a.matchedWords.length;++f){var g=a.matchedWords[f];g in b||(b[g]=1,e=!0)}e&&c.push(a.value)}else if("[object Array]"===Object.prototype.toString.call(a))for(var h=0;h<a.length;++h){var i=d(a[h],b);c=c.concat(i)}else if("object"==typeof a)for(var j in a)a.hasOwnProperty(j)&&(c=c.concat(d(a[j],b)));return c}function e(a,b,c){var f=a._highlightResult||a;if(-1===c.indexOf("."))return c in f?d(f[c],b):[];for(var g=c.split("."),h=f,i=0;i<g.length;++i){if("[object Array]"===Object.prototype.toString.call(h)){for(var j=[],k=0;k<h.length;++k)j=j.concat(e(h[k],b,g.slice(i).join(".")));return j}if(!(g[i]in h))return[];h=h[g[i]]}return d(h,b)}var f={},g={},h=e(a,g,b);if(f.title=h.length>0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i<c.length;++i)for(var j=e(a,g,c[i]),k=0;k<j.length;++k)f.subtitles.push({attr:c[i],value:j[k]});return f}var ALGOLIA_VERSION="2.9.4",AlgoliaSearch=function(a,b,c,d,e){var f=this;this.applicationID=a,this.apiKey=b,this.dsn=!0,this.dsnHost=null,this.hosts=[],this.currentHostIndex=0,this.requestTimeoutInMs=2e3,this.extraHeaders=[],this.jsonp=null,this.options={},this.cache={};var g,h="net";if("string"==typeof c)g=c;else{var i=c||{};this.options=i,this._isUndefined(i.method)||(g=i.method),this._isUndefined(i.tld)||(h=i.tld),this._isUndefined(i.dsn)||(this.dsn=i.dsn),this._isUndefined(i.hosts)||(e=i.hosts),this._isUndefined(i.dsnHost)||(this.dsnHost=i.dsnHost),this._isUndefined(i.requestTimeoutInMs)||(this.requestTimeoutInMs=+i.requestTimeoutInMs),this._isUndefined(i.jsonp)||(this.jsonp=i.jsonp)}this._isUndefined(e)&&(e=[this.applicationID+"-1.algolia."+h,this.applicationID+"-2.algolia."+h,this.applicationID+"-3.algolia."+h]),this.host_protocol="http://",this._isUndefined(g)||null===g?this.host_protocol=("https:"==document.location.protocol?"https":"http")+"://":("https"===g||"HTTPS"===g)&&(this.host_protocol="https://");for(var j=0;j<e.length;++j)Math.random()>.5&&this.hosts.reverse(),this.hosts.push(this.host_protocol+e[j]);Math.random()>.5&&this.hosts.reverse(),(this.dsn||null!=this.dsnHost)&&this.hosts.unshift(this.dsnHost?this.host_protocol+this.dsnHost:this.host_protocol+this.applicationID+"-dsn.algolia."+h),this.options.angular&&this.options.angular.$injector.invoke(["$http","$q",function(a,b){f.options.angular.$q=b,f.options.angular.$http=a}])};AlgoliaSearch.JSONPCounter=0,AlgoliaSearch.prototype={deleteIndex:function(a,b){return this._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(a),callback:b})},moveIndex:function(a,b,c){var d={operation:"move",destination:b};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},copyIndex:function(a,b,c){var d={operation:"copy",destination:b};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},getLogs:function(a,b,c){return this._isUndefined(b)&&(b=0),this._isUndefined(c)&&(c=10),this._jsonRequest({method:"GET",url:"/1/logs?offset="+b+"&length="+c,callback:a})},listIndexes:function(a,b){var c="undefined"!=typeof b?"?page="+b:"";return this._jsonRequest({method:"GET",url:"/1/indexes"+c,callback:a})},initIndex:function(a){return new this.Index(this,a)},listUserKeys:function(a){return this._jsonRequest({method:"GET",url:"/1/keys",callback:a})},getUserKeyACL:function(a,b){return this._jsonRequest({method:"GET",url:"/1/keys/"+a,callback:b})},deleteUserKey:function(a,b){return this._jsonRequest({method:"DELETE",url:"/1/keys/"+a,callback:b})},addUserKey:function(a,b){return this.addUserKeyWithValidity(a,0,0,0,b)},addUserKeyWithValidity:function(a,b,c,d,e){var f={};return f.acl=a,f.validity=b,f.maxQueriesPerIPPerHour=c,f.maxHitsPerQuery=d,this._jsonRequest({method:"POST",url:"/1/keys",body:f,callback:e})},setSecurityTags:function(a){if("[object Array]"===Object.prototype.toString.call(a)){for(var b=[],c=0;c<a.length;++c)if("[object Array]"===Object.prototype.toString.call(a[c])){for(var d=[],e=0;e<a[c].length;++e)d.push(a[c][e]);b.push("("+d.join(",")+")")}else b.push(a[c]);a=b.join(",")}this.tagFilters=a},setUserToken:function(a){this.userToken=a},startQueriesBatch:function(){this.batch=[]},addQueryInBatch:function(a,b,c){var d="query="+encodeURIComponent(b);this._isUndefined(c)||null===c||(d=this._getSearchParams(c,d)),this.batch.push({indexName:a,params:d})},clearCache:function(){this.cache={}},sendQueriesBatch:function(a,b){for(var c=this,d={requests:[]},e=0;e<c.batch.length;++e)d.requests.push(c.batch[e]);if(window.clearTimeout(c.onDelayTrigger),this._isUndefined(b)||null===b||!(b>0))return this._sendQueriesBatch(d,a);var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f},setRequestTimeout:function(a){a&&(this.requestTimeoutInMs=parseInt(a,10))},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null,this.cache={}},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null===this.jsonp){var c=this;return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:function(d,e){d?(c.jsonp=!1,b&&b(d,e)):(c.jsonp=!0,c._sendQueriesBatch(a,b))}})}if(this.jsonp){for(var d="",e=0;e<a.requests.length;++e){var f="/1/indexes/"+encodeURIComponent(a.requests[e].indexName)+"?"+a.requests[e].params;d+=e+"="+encodeURIComponent(f)+"&"}var g={params:d};return this._jsonRequest({cache:this.cache,method:"GET",url:"/1/indexes/*",body:g,callback:b})}return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:b})},_jsonRequest:function(a){var b=this,c=a.callback,d=null,e=a.url,f=null;if(this.options.jQuery?(f=this.options.jQuery.$.Deferred(),f.promise=f.promise()):this.options.angular&&(f=this.options.angular.$q.defer()),this._isUndefined(a.body)||(e=a.url+"_body_"+JSON.stringify(a.body)),!this._isUndefined(a.cache)&&(d=a.cache,!this._isUndefined(d[e])))return!this._isUndefined(c)&&c&&setTimeout(function(){c(!0,d[e])},1),f&&f.resolve(d[e]),f&&f.promise;a.successiveRetryCount=0;var g=function(){if(a.successiveRetryCount>=b.hosts.length){var h={message:"Cannot connect the Algolia's Search API. Please send an email to support@algolia.com to report the issue."};return!b._isUndefined(c)&&c&&(a.successiveRetryCount=0,c(!1,h)),void(f&&f.reject(h))}a.callback=function(h,i,j){i&&!b._isUndefined(a.cache)&&(d[e]=j),!i&&h?(b.currentHostIndex=++b.currentHostIndex%b.hosts.length,a.successiveRetryCount+=1,g()):(a.successiveRetryCount=0,f&&(i?f.resolve(j):f.reject(j)),!b._isUndefined(c)&&c&&c(i,j))},a.hostname=b.hosts[b.currentHostIndex],b._jsonRequestByHost(a)};return g(),f&&f.promise},_jsonRequestByHost:function(a){var b=a.hostname+a.url;this.jsonp?this._makeJsonpRequestByHost(b,a):this.options.jQuery?this._makejQueryRequestByHost(b,a):this.options.angular?this._makeAngularRequestByHost(b,a):this._makeXmlHttpRequestByHost(b,a)},_makeAngularRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var d=0;d<this.extraHeaders.length;++d)a+="&"+this.extraHeaders[d].key+"="+this.extraHeaders[d].value;this.options.angular.$http({url:a,method:b.method,data:c,cache:!1,timeout:this.requestTimeoutInMs*(b.successiveRetryCount+1)}).then(function(a){b.callback(!1,!0,a.data)},function(a){0===a.status?b.callback(!0,!1,a.data):400==a.status||403===a.status||404===a.status?b.callback(!1,!1,a.data):b.callback(!0,!1,a.data)})},_makejQueryRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var d=0;d<this.extraHeaders.length;++d)a+="&"+this.extraHeaders[d].key+"="+this.extraHeaders[d].value;this.options.jQuery.$.ajax(a,{type:b.method,timeout:this.requestTimeoutInMs*(b.successiveRetryCount+1),dataType:"json",data:c,error:function(c,d,e){"timeout"===d?b.callback(!0,!1,{message:"Timeout - Could not connect to endpoint "+a}):400===c.status||403===c.status||404===c.status?b.callback(!1,!1,c.responseJSON):b.callback(!0,!1,{message:e})},success:function(a){b.callback(!1,!0,a)}})},_makeJsonpRequestByHost:function(a,b){if("GET"!==b.method)return void b.callback(!0,!1,{message:"Method "+b.method+" "+a+" is not supported by JSONP."});var c=!1,d=!1;AlgoliaSearch.JSONPCounter+=1;var e,f,g,h=document.getElementsByTagName("head")[0],i=document.createElement("script"),j="algoliaJSONP_"+AlgoliaSearch.JSONPCounter,k=!1;window[j]=function(a){try{delete window[j]}catch(e){window[j]=void 0}if(!d){var f=a&&a.message&&a.status||a&&200,g=200===f,h=!g&&400!==f&&403!==f&&404!==f;c=!0,b.callback(h,g,a)}},i.type="text/javascript",a+="?callback="+j+"&X-Algolia-Application-Id="+this.applicationID+"&X-Algolia-API-Key="+this.apiKey,this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters)),this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken));for(var l=0;l<this.extraHeaders.length;++l)a+="&"+this.extraHeaders[l].key+"="+this.extraHeaders[l].value;b.body&&b.body.params&&(a+="&"+b.body.params),e=setTimeout(function(){d=!0,g(),b.callback(!0,!1,{message:"Timeout - Failed to load JSONP script."})},this.requestTimeoutInMs),f=function(){k||d||(k=!0,g(),c||b.callback(!0,!1,{message:"Failed to load JSONP script."}))},g=function(){clearTimeout(e),i.onload=null,i.onreadystatechange=null,i.onerror=null,h.removeChild(i);try{delete window[j],delete window[j+"_loaded"]}catch(a){window[j]=null,window[j+"_loaded"]=null}},i.onreadystatechange=function(){("loaded"===this.readyState||"complete"===this.readyState)&&f()},i.onload=function(){f()},i.onerror=function(){k||d||(g(),b.callback(!0,!1,{message:"Failed to load JSONP script."}))},i.async=!0,i.defer=!0,i.src=a,h.appendChild(i)},_makeXmlHttpRequestByHost:function(a,b){if(!this._support.cors&&!this._support.hasXDomainRequest)return void b.callback(!1,!1,{message:"CORS not supported"});var c,d,e,f=null,g=this._support.cors?new XMLHttpRequest:new XDomainRequest,h=this;this._isUndefined(b.body)||(f=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var i=0;i<this.extraHeaders.length;++i)a+="&"+this.extraHeaders[i].key+"="+this.extraHeaders[i].value;e=function(){h._support.timeout||(d=!0,g.abort()),b.callback(!0,!1,{message:"Timeout - Could not connect to endpoint "+a})},g instanceof XMLHttpRequest?g.open(b.method,a,!0):g.open(b.method,a),this._support.cors&&null!==f&&"GET"!==b.method&&g.setRequestHeader("Content-type","application/x-www-form-urlencoded"),g.onload=function(){if(!d){h._support.timeout||clearTimeout(c);var a=null;try{a=JSON.parse(g.responseText)}catch(e){}var f=g.status||a&&a.message&&a.status||a&&200,i=200===f||201===f,j=!i&&400!==f&&403!==f&&404!==f;b.callback(j,i,a)}},this._support.timeout?(g.timeout=this.requestTimeoutInMs*(b.successiveRetryCount+1),g.ontimeout=e):c=setTimeout(e,this.requestTimeoutInMs*(b.successiveRetryCount+1)),g.onerror=function(a){d||(h._support.timeout||clearTimeout(c),b.callback(!0,!1,{message:"Could not connect to host",error:a}))},g.send(f)},_getSearchParams:function(a,b){if(this._isUndefined(a)||null===a)return b;for(var c in a)null!==c&&a.hasOwnProperty(c)&&(b+=0===b.length?"?":"&",b+=c+"="+encodeURIComponent("[object Array]"===Object.prototype.toString.call(a[c])?JSON.stringify(a[c]):a[c]));return b},_isUndefined:function(a){return void 0===a},_support:{hasXMLHttpRequest:"XMLHttpRequest"in window,hasXDomainRequest:"XDomainRequest"in window,cors:"withCredentials"in new XMLHttpRequest,timeout:"timeout"in new XMLHttpRequest}},AlgoliaSearch.prototype.Index.prototype={clearCache:function(){this.cache={}},addObject:function(a,b,c){var d=this;return this.as._jsonRequest(this.as._isUndefined(c)?{method:"POST",url:"/1/indexes/"+encodeURIComponent(d.indexName),body:a,callback:b}:{method:"PUT",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(c),body:a,callback:b})},addObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"addObject",body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},getObject:function(a,b,c){"[object Array]"!==Object.prototype.toString.call(b)||c||(c=b,b=null);var d=this,e="";if(!this.as._isUndefined(c)){e="?attributes=";for(var f=0;f<c.length;++f)0!==f&&(e+=","),e+=c[f]}return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(a)+e,callback:b})},partialUpdateObject:function(a,b){var c=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a.objectID)+"/partial",body:a,callback:b})},partialUpdateObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"partialUpdateObject",objectID:a[e].objectID,body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},saveObject:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a.objectID),body:a,callback:b})},saveObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"updateObject",objectID:a[e].objectID,body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},deleteObject:function(a,b){if(null===a||0===a.length)return void b(!1,{message:"empty objectID"});var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a),callback:b})},search:function(a,b,c,d){(void 0===a||null===a)&&(a=""),"function"==typeof a&&(b=a,a=""),"object"!=typeof b||!this.as._isUndefined(c)&&c||(c=b,b=null);var e=this,f="query="+encodeURIComponent(a);if(this.as._isUndefined(c)||null===c||(f=this.as._getSearchParams(c,f)),window.clearTimeout(e.onDelayTrigger),this.as._isUndefined(d)||null===d||!(d>0))return this._search(f,b);var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g},browse:function(a,b,c){+b>0&&(this.as._isUndefined(c)||!c)&&(c=b,b=null);var d=this,e="?page="+a;return this.as._isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){d(a?b.hits:b&&b.message)},a)}},waitTask:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};return d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};return g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){var c={params:a};if(null===this.as.jsonp){var d=this;return this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:function(c,e){var f=e&&e.status;c||f&&4===Math.floor(f/100)||1===Math.floor(f/100)?(d.as.jsonp=!1,b&&b(c,e)):(d.as.jsonp=!0,d._search(a,b))}})}return this.as._jsonRequest(this.as.jsonp?{cache:this.cache,method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName),body:c,callback:b}:{cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:b})},as:null,indexName:null,typeAheadArgs:null,typeAheadValueOption:null},function(){var a=function(a){a=a||{};for(var b=1;b<arguments.length;b++)if(arguments[b])for(var c in arguments[b])arguments[b].hasOwnProperty(c)&&(a[c]=arguments[b][c]);return a};window.AlgoliaSearchHelper=function(b,c,d){var e={facets:[],disjunctiveFacets:[],hitsPerPage:20,defaultFacetFilters:[]};this.init(b,c,a({},e,d))},AlgoliaSearchHelper.prototype={init:function(a,b,c){this.client=a,this.index=b,this.options=c,this.page=0,this.refinements={},this.excludes={},this.disjunctiveRefinements={},this.extraQueries=[]},search:function(a,b,c){this.q=a,this.searchCallback=b,this.searchParams=c||{},this.page=this.page||0,this.refinements=this.refinements||{},this.disjunctiveRefinements=this.disjunctiveRefinements||{},this._search()},clearRefinements:function(){this.disjunctiveRefinements={},this.refinements={}},addDisjunctiveRefine:function(a,b){this.disjunctiveRefinements=this.disjunctiveRefinements||{},this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{},this.disjunctiveRefinements[a][b]=!0},removeDisjunctiveRefine:function(a,b){this.disjunctiveRefinements=this.disjunctiveRefinements||{},this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{};try{delete this.disjunctiveRefinements[a][b]}catch(c){this.disjunctiveRefinements[a][b]=void 0}},addRefine:function(a,b){var c=a+":"+b;this.refinements=this.refinements||{},this.refinements[c]=!0},removeRefine:function(a,b){var c=a+":"+b;this.refinements=this.refinements||{},this.refinements[c]=!1},addExclude:function(a,b){var c=a+":-"+b;this.excludes=this.excludes||{},this.excludes[c]=!0},removeExclude:function(a,b){var c=a+":-"+b;this.excludes=this.excludes||{},this.excludes[c]=!1},toggleExclude:function(a,b){for(var c=0;c<this.options.facets.length;++c)if(this.options.facets[c]==a){var d=a+":-"+b;return this.excludes[d]=!this.excludes[d],this.page=0,this._search(),!0}return!1},toggleRefine:function(a,b){for(var c=0;c<this.options.facets.length;++c)if(this.options.facets[c]==a){var d=a+":"+b;return this.refinements[d]=!this.refinements[d],this.page=0,this._search(),!0}this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{};for(var e=0;e<this.options.disjunctiveFacets.length;++e)if(this.options.disjunctiveFacets[e]==a)return this.disjunctiveRefinements[a][b]=!this.disjunctiveRefinements[a][b],this.page=0,this._search(),!0;return!1},isRefined:function(a,b){var c=a+":"+b;return this.refinements[c]?!0:this.disjunctiveRefinements[a]&&this.disjunctiveRefinements[a][b]?!0:!1},isExcluded:function(a,b){var c=a+":-"+b;return this.excludes[c]?!0:!1},nextPage:function(){this._gotoPage(this.page+1)},previousPage:function(){this.page>0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},setIndex:function(a){this.index=a},getIndex:function(){return this.index},clearExtraQueries:function(){this.extraQueries=[]},addExtraQuery:function(a,b,c){this.extraQueries.push({index:a,query:b,params:c||{}})},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());var a=[],b={},c=0;for(c=0;c<this.options.disjunctiveFacets.length;++c){var d=this.options.disjunctiveFacets[c];this._hasDisjunctiveRefinements(d)?a.push(d):b[d]=!0}for(c=0;c<a.length;++c)this.client.addQueryInBatch(this.index,this.q,this._getDisjunctiveFacetSearchParams(a[c]));for(c=0;c<this.extraQueries.length;++c)this.client.addQueryInBatch(this.extraQueries[c].index,this.extraQueries[c].query,this.extraQueries[c].params);var e=this;this.client.sendQueriesBatch(function(d,f){if(!d)return void e.searchCallback(!1,f);var g=f.results[0];g.disjunctiveFacets=g.disjunctiveFacets||{},g.facets_stats=g.facets_stats||{};for(var h in b)if(g.facets[h]&&!g.disjunctiveFacets[h]){g.disjunctiveFacets[h]=g.facets[h];try{delete g.facets[h]}catch(i){g.facets[h]=void 0}}for(c=0;c<a.length;++c){for(var j in f.results[c+1].facets)if(g.disjunctiveFacets[j]=f.results[c+1].facets[j],e.disjunctiveRefinements[j])for(var k in e.disjunctiveRefinements[j])!g.disjunctiveFacets[j][k]&&e.disjunctiveRefinements[j][k]&&(g.disjunctiveFacets[j][k]=0);for(var l in f.results[c+1].facets_stats)g.facets_stats[l]=f.results[c+1].facets_stats[l]}g.facetStats=g.facets_stats;for(var m in e.excludes)if(e.excludes[m]){var i=m.indexOf(":-"),h=m.slice(0,i),k=m.slice(i+2);g.facets[h]=g.facets[h]||{},g.facets[h][k]||(g.facets[h][k]=0)}if(0===e.extraQueries.length)e.searchCallback(!0,g);else{var n={results:[g]};for(c=0;c<e.extraQueries.length;++c)n.results.push(f.results[1+a.length+c]);e.searchCallback(!0,n)}})},_getHitsSearchParams:function(){var b=[],c=0;for(c=0;c<this.options.facets.length;++c)b.push(this.options.facets[c]);for(c=0;c<this.options.disjunctiveFacets.length;++c){var d=this.options.disjunctiveFacets[c];this._hasDisjunctiveRefinements(d)||b.push(d)}return a({},{hitsPerPage:this.options.hitsPerPage,page:this.page,facets:b,facetFilters:this._getFacetFilters()},this.searchParams)},_getDisjunctiveFacetSearchParams:function(b){return a({},this.searchParams,{hitsPerPage:1,page:0,attributesToRetrieve:[],attributesToHighlight:[],attributesToSnippet:[],facets:b,facetFilters:this._getFacetFilters(b),analytics:!1})},_hasDisjunctiveRefinements:function(a){for(var b in this.disjunctiveRefinements[a])if(this.disjunctiveRefinements[a][b])return!0;return!1},_getFacetFilters:function(a){var b=[];if(this.options.defaultFacetFilters)for(var c=0;c<this.options.defaultFacetFilters.length;++c)b.push(this.options.defaultFacetFilters[c]);for(var d in this.refinements)this.refinements[d]&&b.push(d);for(var d in this.excludes)this.excludes[d]&&b.push(d);for(var e in this.disjunctiveRefinements)if(e!=a){var f=[];for(var g in this.disjunctiveRefinements[e])this.disjunctiveRefinements[e][g]&&f.push(e+":"+g);f.length>0&&b.push(f)}return b}}}(),function(){window.AlgoliaPlaces=function(a,b){this.init(a,b)},AlgoliaPlaces.prototype={init:function(a,b){this.client=new AlgoliaSearch(a,b,"http",!0,["places-1.algolia.io","places-2.algolia.io","places-3.algolia.io"]),this.cache={}},search:function(a,b,c){var d="query="+encodeURIComponent(a);this.client._isUndefined(c)||null==c||(d=this.client._getSearchParams(c,d));var e={params:d,apiKey:this.client.apiKey,appID:this.client.applicationID};this.client._jsonRequest({cache:this.cache,method:"POST",url:"/1/places/query",body:e,callback:b,removeCustomHTTPHeaders:!0})}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
@@ -0,0 +1,1717 @@
1
+ /*! algoliasearch 3.0.3 | © 2014, 2015 Algolia SAS | github.com/algolia/algoliasearch-client-js */
2
+ (function(f){var g;if(typeof window!=='undefined'){g=window}else if(typeof self!=='undefined'){g=self}g.ALGOLIA_MIGRATION_LAYER=f()})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
3
+
4
+ module.exports = function load (src, opts, cb) {
5
+ var head = document.head || document.getElementsByTagName('head')[0]
6
+ var script = document.createElement('script')
7
+
8
+ if (typeof opts === 'function') {
9
+ cb = opts
10
+ opts = {}
11
+ }
12
+
13
+ opts = opts || {}
14
+ cb = cb || function() {}
15
+
16
+ script.type = opts.type || 'text/javascript'
17
+ script.charset = opts.charset || 'utf8';
18
+ script.async = 'async' in opts ? !!opts.async : true
19
+ script.src = src
20
+
21
+ if (opts.attrs) {
22
+ setAttributes(script, opts.attrs)
23
+ }
24
+
25
+ if (opts.text) {
26
+ script.text = '' + opts.text
27
+ }
28
+
29
+ var onend = 'onload' in script ? stdOnEnd : ieOnEnd
30
+ onend(script, cb)
31
+
32
+ // some good legacy browsers (firefox) fail the 'in' detection above
33
+ // so as a fallback we always set onload
34
+ // old IE will ignore this and new IE will set onload
35
+ if (!script.onload) {
36
+ stdOnEnd(script, cb);
37
+ }
38
+
39
+ head.appendChild(script)
40
+ }
41
+
42
+ function setAttributes(script, attrs) {
43
+ for (var attr in attrs) {
44
+ script.setAttribute(attr, attrs[attr]);
45
+ }
46
+ }
47
+
48
+ function stdOnEnd (script, cb) {
49
+ script.onload = function () {
50
+ this.onerror = this.onload = null
51
+ cb(null, script)
52
+ }
53
+ script.onerror = function () {
54
+ // this.onload = null here is necessary
55
+ // because even IE9 works not like others
56
+ this.onerror = this.onload = null
57
+ cb(new Error('Failed to load ' + this.src), script)
58
+ }
59
+ }
60
+
61
+ function ieOnEnd (script, cb) {
62
+ script.onreadystatechange = function () {
63
+ if (this.readyState != 'complete' && this.readyState != 'loaded') return
64
+ this.onreadystatechange = null
65
+ cb(null, script) // there is no way to catch loading errors in IE8
66
+ }
67
+ }
68
+
69
+ },{}],2:[function(require,module,exports){
70
+ // this module helps finding if the current page is using
71
+ // the cdn.jsdelivr.net/algoliasearch/latest/$BUILDNAME.min.js version
72
+
73
+ module.exports = isUsingLatest;
74
+
75
+ function isUsingLatest(buildName) {
76
+ var toFind = new RegExp('cdn\\.jsdelivr\\.net/algoliasearch/latest/' +
77
+ buildName.replace('.', '\\.') + // algoliasearch, algoliasearch.angular
78
+ '(?:\\.min)?\\.js$'); // [.min].js
79
+
80
+ var scripts = document.getElementsByTagName('script');
81
+ var found = false;
82
+ for (var currentScript = 0, nbScripts = scripts.length;
83
+ currentScript < nbScripts;
84
+ currentScript++) {
85
+ if (scripts[currentScript].src && toFind.test(scripts[currentScript].src)) {
86
+ found = true;
87
+ break;
88
+ }
89
+ }
90
+
91
+ return found;
92
+ }
93
+
94
+ },{}],3:[function(require,module,exports){
95
+ (function (global){
96
+ module.exports = loadV2;
97
+
98
+ function loadV2(buildName) {
99
+ var loadScript = require(1);
100
+ var v2ScriptUrl = '//cdn.jsdelivr.net/algoliasearch/2/' + buildName + '.min.js';
101
+
102
+ var message =
103
+ '-- AlgoliaSearch `latest` warning --\n' +
104
+ 'Warning, you are using the `latest` version string from jsDelivr to load the AlgoliaSearch library.\n' +
105
+ 'Using `latest` is no more recommended, you should load //cdn.jsdelivr.net/algoliasearch/2/algoliasearch.min.js\n\n' +
106
+ 'Also, we updated the AlgoliaSearch JavaScript client to V3. If you want to upgrade,\n' +
107
+ 'please read our migration guide at https://github.com/algolia/algoliasearch-client-js/wiki/Migration-guide-from-2.x.x-to-3.x.x\n' +
108
+ '-- /AlgoliaSearch `latest` warning --';
109
+
110
+ if (global.console) {
111
+ if (global.console.warn) {
112
+ global.console.warn(message);
113
+ } else if (global.console.log) {
114
+ global.console.log(message);
115
+ }
116
+ }
117
+
118
+ // If current script loaded asynchronously,
119
+ // it will load the script with DOMElement
120
+ // otherwise, it will load the script with document.write
121
+ try {
122
+ // why \x3c? http://stackoverflow.com/a/236106/147079
123
+ document.write('\x3Cscript>window.ALGOLIA_SUPPORTS_DOCWRITE = true\x3C/script>');
124
+
125
+ if (global.ALGOLIA_SUPPORTS_DOCWRITE === true) {
126
+ document.write('\x3Cscript src="' + v2ScriptUrl + '">\x3C/script>');
127
+ scriptLoaded('document.write')();
128
+ } else {
129
+ loadScript(v2ScriptUrl, scriptLoaded('DOMElement'));
130
+ }
131
+ } catch(e) {
132
+ loadScript(v2ScriptUrl, scriptLoaded('DOMElement'));
133
+ }
134
+ }
135
+
136
+ function scriptLoaded(method) {
137
+ return function log() {
138
+ var message = 'AlgoliaSearch: loaded V2 script using ' + method;
139
+
140
+ global.console && global.console.log && global.console.log(message);
141
+ };
142
+ }
143
+
144
+ }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
145
+ },{"1":1}],4:[function(require,module,exports){
146
+ (function (global){
147
+ /*global AlgoliaExplainResults:true*/
148
+ /*eslint no-unused-vars: [2, {"vars": "local"}]*/
149
+
150
+ module.exports = oldGlobals;
151
+
152
+ // put old window.AlgoliaSearch.. into window. again so that
153
+ // users upgrading to V3 without changing their code, will be warned
154
+ function oldGlobals() {
155
+ var message =
156
+ '-- AlgoliaSearch V2 => V3 error --\n' +
157
+ 'You are trying to use a new version of the AlgoliaSearch JavaScript client with an old notation.\n' +
158
+ 'Please read our migration guide at https://github.com/algolia/algoliasearch-client-js/wiki/Migration-guide-from-2.x.x-to-3.x.x\n' +
159
+ '-- /AlgoliaSearch V2 => V3 error --';
160
+
161
+ global.AlgoliaSearch = function() {
162
+ throw new Error(message);
163
+ };
164
+
165
+ global.AlgoliaSearchHelper = function() {
166
+ throw new Error(message);
167
+ };
168
+
169
+ // cannot use window.AlgoliaExplainResults on old IEs, dunno why
170
+ AlgoliaExplainResults = function() {
171
+ throw new Error(message);
172
+ };
173
+ }
174
+
175
+ }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
176
+ },{}],5:[function(require,module,exports){
177
+ // This script will be browserified and prepended to the normal build
178
+ // directly in window, not wrapped in any module definition
179
+ // To avoid cases where we are loaded with /latest/ along with
180
+ migrationLayer("algoliasearch.angular");
181
+
182
+ // Now onto the V2 related code:
183
+ // If the client is using /latest/$BUILDNAME.min.js, load V2 of the library
184
+ //
185
+ // Otherwise, setup a migration layer that will throw on old constructors like
186
+ // new AlgoliaSearch().
187
+ // So that users upgrading from v2 to v3 will have a clear information
188
+ // message on what to do if they did not read the migration guide
189
+ function migrationLayer(buildName) {
190
+ var isUsingLatest = require(2);
191
+ var loadV2 = require(3);
192
+ var oldGlobals = require(4);
193
+
194
+ if (isUsingLatest(buildName)) {
195
+ loadV2(buildName);
196
+ } else {
197
+ oldGlobals();
198
+ }
199
+ }
200
+
201
+ },{"2":2,"3":3,"4":4}]},{},[5])(5)
202
+ });(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
203
+ // shim for using process in browser
204
+
205
+ var process = module.exports = {};
206
+ var queue = [];
207
+ var draining = false;
208
+
209
+ function drainQueue() {
210
+ if (draining) {
211
+ return;
212
+ }
213
+ draining = true;
214
+ var currentQueue;
215
+ var len = queue.length;
216
+ while(len) {
217
+ currentQueue = queue;
218
+ queue = [];
219
+ var i = -1;
220
+ while (++i < len) {
221
+ currentQueue[i]();
222
+ }
223
+ len = queue.length;
224
+ }
225
+ draining = false;
226
+ }
227
+ process.nextTick = function (fun) {
228
+ queue.push(fun);
229
+ if (!draining) {
230
+ setTimeout(drainQueue, 0);
231
+ }
232
+ };
233
+
234
+ process.title = 'browser';
235
+ process.browser = true;
236
+ process.env = {};
237
+ process.argv = [];
238
+ process.version = ''; // empty string to avoid regexp issues
239
+ process.versions = {};
240
+
241
+ function noop() {}
242
+
243
+ process.on = noop;
244
+ process.addListener = noop;
245
+ process.once = noop;
246
+ process.off = noop;
247
+ process.removeListener = noop;
248
+ process.removeAllListeners = noop;
249
+ process.emit = noop;
250
+
251
+ process.binding = function (name) {
252
+ throw new Error('process.binding is not supported');
253
+ };
254
+
255
+ // TODO(shtylman)
256
+ process.cwd = function () { return '/' };
257
+ process.chdir = function (dir) {
258
+ throw new Error('process.chdir is not supported');
259
+ };
260
+ process.umask = function() { return 0; };
261
+
262
+ },{}],2:[function(require,module,exports){
263
+ (function (process){
264
+ module.exports = AlgoliaSearch;
265
+
266
+ /*
267
+ * Algolia Search library initialization
268
+ * https://www.algolia.com/
269
+ *
270
+ * @param {string} applicationID - Your applicationID, found in your dashboard
271
+ * @param {string} apiKey - Your API key, found in your dashboard
272
+ * @param {Object} [opts]
273
+ * @param {number} [opts.timeout=2000] - The request timeout set in milliseconds, another request will be issued after this timeout
274
+ * @param {string} [opts.protocol='http:'] - The protocol used to query Algolia Search API.
275
+ * Set to 'https:' to force using https. Default to document.location.protocol in browsers
276
+ * @param {string[]} [opts.hosts=[
277
+ * this.applicationID + '-1.algolia.' + opts.tld,
278
+ * this.applicationID + '-2.algolia.' + opts.tld,
279
+ * this.applicationID + '-3.algolia.' + opts.tld]
280
+ * ] - The hosts to use for Algolia Search API. It this your responsibility to shuffle the hosts and add a DSN host in it
281
+ * @param {string} [opts.tld='net'] - The tld to use when computing hosts default list
282
+ */
283
+ function AlgoliaSearch(applicationID, apiKey, opts, _request) {
284
+ var usage = 'Usage: algoliasearch(applicationID, apiKey, opts)';
285
+
286
+ if (!applicationID) {
287
+ throw new Error('Please provide an application ID. ' + usage);
288
+ }
289
+
290
+ if (!apiKey) {
291
+ throw new Error('Please provide an API key. ' + usage);
292
+ }
293
+
294
+ opts = opts || {};
295
+
296
+ // now setting default options
297
+ // could not find a tiny module to do that, let's go manual
298
+ if (opts.timeout === undefined) {
299
+ opts.timeout = 2000;
300
+ }
301
+
302
+ if (opts.protocol === undefined) {
303
+ opts.protocol = document && document.location.protocol || 'http:';
304
+ }
305
+
306
+ if (opts.hosts === undefined) {
307
+ opts.hosts = []; // filled later on, has dependencies
308
+ }
309
+
310
+ if (opts.tld === undefined) {
311
+ opts.tld = 'net';
312
+ }
313
+
314
+ // while we advocate for colon-at-the-end values: 'http:' for `opts.protocol`
315
+ // we also accept `http` and `https`. It's a common error.
316
+ if (!/:$/.test(opts.protocol)) {
317
+ opts.protocol = opts.protocol + ':';
318
+ }
319
+
320
+ // no hosts given, add defaults
321
+ if (opts.hosts.length === 0) {
322
+ opts.hosts = shuffle([
323
+ applicationID + '-1.algolia.' + opts.tld,
324
+ applicationID + '-2.algolia.' + opts.tld,
325
+ applicationID + '-3.algolia.' + opts.tld
326
+ ]);
327
+
328
+ // add default dsn host
329
+ opts.hosts.unshift(applicationID + '-dsn.algolia.' + opts.tld);
330
+ }
331
+
332
+ opts.hosts = map(opts.hosts, function prependProtocol(host) {
333
+ return opts.protocol + '//' + host;
334
+ });
335
+
336
+ this.applicationID = applicationID;
337
+ this.apiKey = apiKey;
338
+ this.hosts = opts.hosts;
339
+
340
+ this.currentHostIndex = 0;
341
+ this.requestTimeout = opts.timeout;
342
+ this.extraHeaders = [];
343
+ this.cache = {};
344
+ this._request = _request;
345
+ }
346
+
347
+ AlgoliaSearch.prototype = {
348
+ /*
349
+ * Delete an index
350
+ *
351
+ * @param indexName the name of index to delete
352
+ * @param callback the result callback with two arguments
353
+ * error: null or Error('message')
354
+ * content: the server answer that contains the task ID
355
+ */
356
+ deleteIndex: function(indexName, callback) {
357
+ return this._jsonRequest({ method: 'DELETE',
358
+ url: '/1/indexes/' + encodeURIComponent(indexName),
359
+ callback: callback });
360
+ },
361
+ /**
362
+ * Move an existing index.
363
+ * @param srcIndexName the name of index to copy.
364
+ * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
365
+ * @param callback the result callback with two arguments
366
+ * error: null or Error('message')
367
+ * content: the server answer that contains the task ID
368
+ */
369
+ moveIndex: function(srcIndexName, dstIndexName, callback) {
370
+ var postObj = {operation: 'move', destination: dstIndexName};
371
+ return this._jsonRequest({ method: 'POST',
372
+ url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
373
+ body: postObj,
374
+ callback: callback });
375
+
376
+ },
377
+ /**
378
+ * Copy an existing index.
379
+ * @param srcIndexName the name of index to copy.
380
+ * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
381
+ * @param callback the result callback with two arguments
382
+ * error: null or Error('message')
383
+ * content: the server answer that contains the task ID
384
+ */
385
+ copyIndex: function(srcIndexName, dstIndexName, callback) {
386
+ var postObj = {operation: 'copy', destination: dstIndexName};
387
+ return this._jsonRequest({ method: 'POST',
388
+ url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
389
+ body: postObj,
390
+ callback: callback });
391
+ },
392
+ /**
393
+ * Return last log entries.
394
+ * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry).
395
+ * @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000.
396
+ * @param callback the result callback with two arguments
397
+ * error: null or Error('message')
398
+ * content: the server answer that contains the task ID
399
+ */
400
+ getLogs: function(offset, length, callback) {
401
+ if (arguments.length === 0 || typeof offset === 'function') {
402
+ // getLogs([cb])
403
+ callback = offset;
404
+ offset = 0;
405
+ length = 10;
406
+ } else if (arguments.length === 1 || typeof length === 'function') {
407
+ // getLogs(1, [cb)]
408
+ callback = length;
409
+ length = 10;
410
+ }
411
+
412
+ return this._jsonRequest({ method: 'GET',
413
+ url: '/1/logs?offset=' + offset + '&length=' + length,
414
+ callback: callback });
415
+ },
416
+ /*
417
+ * List all existing indexes (paginated)
418
+ *
419
+ * @param page The page to retrieve, starting at 0.
420
+ * @param callback the result callback with two arguments
421
+ * error: null or Error('message')
422
+ * content: the server answer with index list
423
+ */
424
+ listIndexes: function(page, callback) {
425
+ var params = '';
426
+
427
+ if (page === undefined || typeof page === 'function') {
428
+ callback = page;
429
+ } else {
430
+ params = '?page=' + page;
431
+ }
432
+
433
+ return this._jsonRequest({ method: 'GET',
434
+ url: '/1/indexes' + params,
435
+ callback: callback });
436
+ },
437
+
438
+ /*
439
+ * Get the index object initialized
440
+ *
441
+ * @param indexName the name of index
442
+ * @param callback the result callback with one argument (the Index instance)
443
+ */
444
+ initIndex: function(indexName) {
445
+ return new this.Index(this, indexName);
446
+ },
447
+ /*
448
+ * List all existing user keys with their associated ACLs
449
+ *
450
+ * @param callback the result callback with two arguments
451
+ * error: null or Error('message')
452
+ * content: the server answer with user keys list
453
+ */
454
+ listUserKeys: function(callback) {
455
+ return this._jsonRequest({ method: 'GET',
456
+ url: '/1/keys',
457
+ callback: callback });
458
+ },
459
+ /*
460
+ * Get ACL of a user key
461
+ *
462
+ * @param key
463
+ * @param callback the result callback with two arguments
464
+ * error: null or Error('message')
465
+ * content: the server answer with user keys list
466
+ */
467
+ getUserKeyACL: function(key, callback) {
468
+ return this._jsonRequest({ method: 'GET',
469
+ url: '/1/keys/' + key,
470
+ callback: callback });
471
+ },
472
+ /*
473
+ * Delete an existing user key
474
+ * @param key
475
+ * @param callback the result callback with two arguments
476
+ * error: null or Error('message')
477
+ * content: the server answer with user keys list
478
+ */
479
+ deleteUserKey: function(key, callback) {
480
+ return this._jsonRequest({ method: 'DELETE',
481
+ url: '/1/keys/' + key,
482
+ callback: callback });
483
+ },
484
+ /*
485
+ * Add an existing user key
486
+ *
487
+ * @param acls the list of ACL for this key. Defined by an array of strings that
488
+ * can contains the following values:
489
+ * - search: allow to search (https and http)
490
+ * - addObject: allows to add/update an object in the index (https only)
491
+ * - deleteObject : allows to delete an existing object (https only)
492
+ * - deleteIndex : allows to delete index content (https only)
493
+ * - settings : allows to get index settings (https only)
494
+ * - editSettings : allows to change index settings (https only)
495
+ * @param callback the result callback with two arguments
496
+ * error: null or Error('message')
497
+ * content: the server answer with user keys list
498
+ */
499
+ addUserKey: function(acls, callback) {
500
+ return this.addUserKeyWithValidity(acls, {
501
+ validity: 0,
502
+ maxQueriesPerIPPerHour: 0,
503
+ maxHitsPerQuery: 0
504
+ }, callback);
505
+ },
506
+ /*
507
+ * Add an existing user key
508
+ *
509
+ * @param acls the list of ACL for this key. Defined by an array of strings that
510
+ * can contains the following values:
511
+ * - search: allow to search (https and http)
512
+ * - addObject: allows to add/update an object in the index (https only)
513
+ * - deleteObject : allows to delete an existing object (https only)
514
+ * - deleteIndex : allows to delete index content (https only)
515
+ * - settings : allows to get index settings (https only)
516
+ * - editSettings : allows to change index settings (https only)
517
+ * @param params.validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
518
+ * @param params.maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
519
+ * @param params.maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
520
+ * @param callback the result callback with two arguments
521
+ * error: null or Error('message')
522
+ * content: the server answer with user keys list
523
+ */
524
+ addUserKeyWithValidity: function(acls, params, callback) {
525
+ var aclsObject = {};
526
+ aclsObject.acl = acls;
527
+ aclsObject.validity = params.validity;
528
+ aclsObject.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour;
529
+ aclsObject.maxHitsPerQuery = params.maxHitsPerQuery;
530
+ return this._jsonRequest({ method: 'POST',
531
+ url: '/1/keys',
532
+ body: aclsObject,
533
+ callback: callback });
534
+ },
535
+
536
+ /**
537
+ * Set the extra security tagFilters header
538
+ * @param {string|array} tags The list of tags defining the current security filters
539
+ */
540
+ setSecurityTags: function(tags) {
541
+ if (Object.prototype.toString.call(tags) === '[object Array]') {
542
+ var strTags = [];
543
+ for (var i = 0; i < tags.length; ++i) {
544
+ if (Object.prototype.toString.call(tags[i]) === '[object Array]') {
545
+ var oredTags = [];
546
+ for (var j = 0; j < tags[i].length; ++j) {
547
+ oredTags.push(tags[i][j]);
548
+ }
549
+ strTags.push('(' + oredTags.join(',') + ')');
550
+ } else {
551
+ strTags.push(tags[i]);
552
+ }
553
+ }
554
+ tags = strTags.join(',');
555
+ }
556
+ this.tagFilters = tags;
557
+ },
558
+
559
+ /**
560
+ * Set the extra user token header
561
+ * @param {string} userToken The token identifying a uniq user (used to apply rate limits)
562
+ */
563
+ setUserToken: function(userToken) {
564
+ this.userToken = userToken;
565
+ },
566
+
567
+ /*
568
+ * Initialize a new batch of search queries
569
+ */
570
+ startQueriesBatch: function() {
571
+ this.batch = [];
572
+ },
573
+ /*
574
+ * Add a search query in the batch
575
+ *
576
+ * @param query the full text query
577
+ * @param args (optional) if set, contains an object with query parameters:
578
+ * - attributes: an array of object attribute names to retrieve
579
+ * (if not set all attributes are retrieve)
580
+ * - attributesToHighlight: an array of object attribute names to highlight
581
+ * (if not set indexed attributes are highlighted)
582
+ * - minWordSizefor1Typo: the minimum number of characters to accept one typo.
583
+ * Defaults to 3.
584
+ * - minWordSizefor2Typos: the minimum number of characters to accept two typos.
585
+ * Defaults to 7.
586
+ * - getRankingInfo: if set, the result hits will contain ranking information in
587
+ * _rankingInfo attribute
588
+ * - page: (pagination parameter) page to retrieve (zero base). Defaults to 0.
589
+ * - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10.
590
+ */
591
+ addQueryInBatch: function(indexName, query, args) {
592
+ var params = 'query=' + encodeURIComponent(query);
593
+ if (!this._isUndefined(args) && args !== null) {
594
+ params = this._getSearchParams(args, params);
595
+ }
596
+ this.batch.push({ indexName: indexName, params: params });
597
+ },
598
+ /*
599
+ * Clear all queries in cache
600
+ */
601
+ clearCache: function() {
602
+ this.cache = {};
603
+ },
604
+ /*
605
+ * Launch the batch of queries using XMLHttpRequest.
606
+ * (Optimized for browser using a POST query to minimize number of OPTIONS queries)
607
+ *
608
+ * @param callback the function that will receive results
609
+ */
610
+ sendQueriesBatch: function(callback) {
611
+ var as = this;
612
+ var params = {requests: []};
613
+
614
+ for (var i = 0; i < as.batch.length; ++i) {
615
+ params.requests.push(as.batch[i]);
616
+ }
617
+
618
+ return this._sendQueriesBatch(params, callback);
619
+ },
620
+
621
+ /**
622
+ * Set the number of milliseconds a request can take before automatically being terminated.
623
+ *
624
+ * @param {Number} milliseconds
625
+ */
626
+ setRequestTimeout: function(milliseconds) {
627
+ if (milliseconds) {
628
+ this.requestTimeout = parseInt(milliseconds, 10);
629
+ }
630
+ },
631
+
632
+ /*
633
+ * Index class constructor.
634
+ * You should not use this method directly but use initIndex() function
635
+ */
636
+ Index: function(algoliasearch, indexName) {
637
+ this.indexName = indexName;
638
+ this.as = algoliasearch;
639
+ this.typeAheadArgs = null;
640
+ this.typeAheadValueOption = null;
641
+
642
+ // make sure every index instance has it's own cache
643
+ this.cache = {};
644
+ },
645
+ /**
646
+ * Add an extra field to the HTTP request
647
+ *
648
+ * @param key the header field name
649
+ * @param value the header field value
650
+ */
651
+ setExtraHeader: function(key, value) {
652
+ this.extraHeaders.push({ key: key, value: value});
653
+ },
654
+
655
+ _sendQueriesBatch: function(params, callback) {
656
+ return this._jsonRequest({ cache: this.cache,
657
+ method: 'POST',
658
+ url: '/1/indexes/*/queries',
659
+ body: params,
660
+ fallback: {
661
+ method: 'GET',
662
+ url: '/1/indexes/*',
663
+ body: {params: (function() {
664
+ var reqParams = '';
665
+ for (var i = 0; i < params.requests.length; ++i) {
666
+ var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params;
667
+ reqParams += i + '=' + encodeURIComponent(q) + '&';
668
+ }
669
+ return reqParams;
670
+ }())}
671
+ },
672
+ callback: callback
673
+ });
674
+ },
675
+ /*
676
+ * Wrapper that try all hosts to maximize the quality of service
677
+ */
678
+ _jsonRequest: function(opts) {
679
+ // handle opts.fallback, automatically use fallback (JSONP in browser plugins, wrapped with $plugin-promises)
680
+ // so if an error occurs and max tries => use fallback
681
+ // set tries to 0 again
682
+ // if fallback used and no more tries, return error
683
+ // fallback parameters are in opts.fallback
684
+ // call request.fallback or request accordingly, same promise chain otherwise
685
+ // put callback& params in front if problem
686
+ var cache = opts.cache;
687
+ var cacheID = opts.url;
688
+ var client = this;
689
+ var tries = 0;
690
+
691
+ // as we use POST requests to pass parameters (like query='aa'),
692
+ // the cacheID must be different between calls
693
+ if (opts.body !== undefined) {
694
+ cacheID += '_body_' + JSON.stringify(opts.body);
695
+ }
696
+
697
+ function doRequest(requester, reqOpts) {
698
+ // handle cache existence
699
+ if (cache && cache[cacheID] !== undefined) {
700
+ return client._request.resolve(cache[cacheID]);
701
+ }
702
+
703
+ if (tries >= client.hosts.length) {
704
+ if (!opts.fallback || requester === client._request.fallback) {
705
+ // could not get a response even using the fallback if one was available
706
+ return client._request.reject(new Error(
707
+ 'Cannot connect to the AlgoliaSearch API.' +
708
+ ' Send an email to support@algolia.com to report and resolve the issue.'
709
+ ));
710
+ }
711
+
712
+ tries = 0;
713
+ reqOpts.method = opts.fallback.method;
714
+ reqOpts.url = opts.fallback.url;
715
+ reqOpts.body = opts.fallback.body;
716
+ reqOpts.timeout = client.requestTimeout * (tries + 1);
717
+ client.currentHostIndex = 0;
718
+ client.forceFallback = true;
719
+ return doRequest(client._request.fallback, reqOpts);
720
+ }
721
+
722
+ var url = reqOpts.url;
723
+
724
+ url += (url.indexOf('?') === -1 ? '?' : '&') + 'X-Algolia-API-Key=' + client.apiKey;
725
+ url += '&X-Algolia-Application-Id=' + client.applicationID;
726
+
727
+ if (client.userToken) {
728
+ url += '&X-Algolia-UserToken=' + encodeURIComponent(client.userToken);
729
+ }
730
+
731
+ if (client.tagFilters) {
732
+ url += '&X-Algolia-TagFilters=' + encodeURIComponent(client.tagFilters);
733
+ }
734
+
735
+ for (var i = 0; i < client.extraHeaders.length; ++i) {
736
+ url += '&' + client.extraHeaders[i].key + '=' + client.extraHeaders[i].value;
737
+ }
738
+
739
+ return requester(client.hosts[client.currentHostIndex] + url, {
740
+ body: reqOpts.body,
741
+ method: reqOpts.method,
742
+ timeout: reqOpts.timeout
743
+ })
744
+ .then(function success(httpResponse) {
745
+ // timeout case, retry immediately
746
+ if (httpResponse instanceof Error) {
747
+ return retryRequest();
748
+ }
749
+
750
+ var status =
751
+ // When in browser mode, using XDR or JSONP
752
+ // We rely on our own API response `status`, only
753
+ // provided when an error occurs, we also expect a .message along
754
+ // Otherwise, it could be a `waitTask` status, that's the only
755
+ // case where we have a response.status that's not the http statusCode
756
+ httpResponse && httpResponse.body && httpResponse.body.message && httpResponse.body.status ||
757
+
758
+ // this is important to check the request statusCode AFTER the body eventual
759
+ // statusCode because some implementations (jQuery XDomainRequest transport) may
760
+ // send statusCode 200 while we had an error
761
+ httpResponse.statusCode ||
762
+
763
+ // When in browser mode, using XDR or JSONP
764
+ // we default to success when no error (no response.status && response.message)
765
+ // If there was a JSON.parse() error then body is null and it fails
766
+ httpResponse && httpResponse.body && 200;
767
+
768
+ var ok = status === 200 || status === 201;
769
+ var retry = !ok && Math.floor(status / 100) !== 4 && Math.floor(status / 100) !== 1;
770
+
771
+ if (ok && cache) {
772
+ cache[cacheID] = httpResponse.body;
773
+ }
774
+
775
+ if (ok) {
776
+ return httpResponse.body;
777
+ }
778
+
779
+ if (retry) {
780
+ return retryRequest();
781
+ }
782
+
783
+ var unrecoverableError = new Error(
784
+ httpResponse.body && httpResponse.body.message || 'Unknown error'
785
+ );
786
+
787
+ return client._request.reject(unrecoverableError);
788
+ }, tryFallback);
789
+
790
+ function retryRequest() {
791
+ client.currentHostIndex = ++client.currentHostIndex % client.hosts.length;
792
+ tries += 1;
793
+ reqOpts.timeout = client.requestTimeout * (tries + 1);
794
+ return doRequest(requester, reqOpts);
795
+ }
796
+
797
+ function tryFallback() {
798
+ // if we are switching to fallback right now, set tries to maximum
799
+ if (!client.forceFallback) {
800
+ // next time doRequest is called, simulate we tried all hosts
801
+ tries = client.hosts.length;
802
+ } else {
803
+ // we were already using the fallback, but something went wrong (script error)
804
+ client.currentHostIndex = ++client.currentHostIndex % client.hosts.length;
805
+ tries += 1;
806
+ }
807
+
808
+ return doRequest(requester, reqOpts);
809
+ }
810
+ }
811
+
812
+ // we can use a fallback if forced AND fallback parameters are available
813
+ var useFallback = client.forceFallback && opts.fallback;
814
+ var requestOptions = useFallback ? opts.fallback : opts;
815
+
816
+ var promise = doRequest(
817
+ useFallback ? client._request.fallback : client._request, {
818
+ url: requestOptions.url,
819
+ method: requestOptions.method,
820
+ body: requestOptions.body,
821
+ timeout: client.requestTimeout * (tries + 1)
822
+ }
823
+ );
824
+
825
+ // either we have a callback
826
+ // either we are using promises
827
+ if (opts.callback) {
828
+ promise.then(function okCb(content) {
829
+ process.nextTick(function() {
830
+ opts.callback(null, content);
831
+ });
832
+ }, function nookCb(err) {
833
+ process.nextTick(function() {
834
+ opts.callback(err);
835
+ });
836
+ });
837
+ } else {
838
+ return promise;
839
+ }
840
+ },
841
+
842
+ /*
843
+ * Transform search param object in query string
844
+ */
845
+ _getSearchParams: function(args, params) {
846
+ if (this._isUndefined(args) || args === null) {
847
+ return params;
848
+ }
849
+ for (var key in args) {
850
+ if (key !== null && args.hasOwnProperty(key)) {
851
+ params += params === '' ? '' : '&';
852
+ params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]);
853
+ }
854
+ }
855
+ return params;
856
+ },
857
+ _isUndefined: function(obj) {
858
+ return obj === void 0;
859
+ }
860
+ };
861
+
862
+ /*
863
+ * Contains all the functions related to one index
864
+ * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object
865
+ */
866
+ AlgoliaSearch.prototype.Index.prototype = {
867
+ /*
868
+ * Clear all queries in cache
869
+ */
870
+ clearCache: function() {
871
+ this.cache = {};
872
+ },
873
+ /*
874
+ * Add an object in this index
875
+ *
876
+ * @param content contains the javascript object to add inside the index
877
+ * @param objectID (optional) an objectID you want to attribute to this object
878
+ * (if the attribute already exist the old object will be overwrite)
879
+ * @param callback (optional) the result callback with two arguments:
880
+ * error: null or Error('message')
881
+ * content: the server answer that contains 3 elements: createAt, taskId and objectID
882
+ */
883
+ addObject: function(content, objectID, callback) {
884
+ var indexObj = this;
885
+
886
+ if (arguments.length === 1 || typeof objectID === 'function') {
887
+ callback = objectID;
888
+ objectID = undefined;
889
+ }
890
+
891
+ return this.as._jsonRequest({
892
+ method: objectID !== undefined ?
893
+ 'PUT' : // update or create
894
+ 'POST', // create (API generates an objectID)
895
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + // create
896
+ (objectID !== undefined ? '/' + encodeURIComponent(objectID) : ''), // update or create
897
+ body: content,
898
+ callback: callback
899
+ });
900
+ },
901
+ /*
902
+ * Add several objects
903
+ *
904
+ * @param objects contains an array of objects to add
905
+ * @param callback (optional) the result callback with two arguments:
906
+ * error: null or Error('message')
907
+ * content: the server answer that updateAt and taskID
908
+ */
909
+ addObjects: function(objects, callback) {
910
+ var indexObj = this;
911
+ var postObj = {requests: []};
912
+ for (var i = 0; i < objects.length; ++i) {
913
+ var request = { action: 'addObject',
914
+ body: objects[i] };
915
+ postObj.requests.push(request);
916
+ }
917
+ return this.as._jsonRequest({ method: 'POST',
918
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
919
+ body: postObj,
920
+ callback: callback });
921
+ },
922
+ /*
923
+ * Get an object from this index
924
+ *
925
+ * @param objectID the unique identifier of the object to retrieve
926
+ * @param attrs (optional) if set, contains the array of attribute names to retrieve
927
+ * @param callback (optional) the result callback with two arguments
928
+ * error: null or Error('message')
929
+ * content: the object to retrieve or the error message if a failure occured
930
+ */
931
+ getObject: function(objectID, attrs, callback) {
932
+ var indexObj = this;
933
+
934
+ if (arguments.length === 1 || typeof attrs === 'function') {
935
+ callback = attrs;
936
+ attrs = undefined;
937
+ }
938
+
939
+ var params = '';
940
+ if (attrs !== undefined) {
941
+ params = '?attributes=';
942
+ for (var i = 0; i < attrs.length; ++i) {
943
+ if (i !== 0) {
944
+ params += ',';
945
+ }
946
+ params += attrs[i];
947
+ }
948
+ }
949
+
950
+ return this.as._jsonRequest({
951
+ method: 'GET',
952
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params,
953
+ callback: callback
954
+ });
955
+ },
956
+
957
+ /*
958
+ * Update partially an object (only update attributes passed in argument)
959
+ *
960
+ * @param partialObject contains the javascript attributes to override, the
961
+ * object must contains an objectID attribute
962
+ * @param callback (optional) the result callback with two arguments:
963
+ * error: null or Error('message')
964
+ * content: the server answer that contains 3 elements: createAt, taskId and objectID
965
+ */
966
+ partialUpdateObject: function(partialObject, callback) {
967
+ var indexObj = this;
968
+ return this.as._jsonRequest({ method: 'POST',
969
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial',
970
+ body: partialObject,
971
+ callback: callback });
972
+ },
973
+ /*
974
+ * Partially Override the content of several objects
975
+ *
976
+ * @param objects contains an array of objects to update (each object must contains a objectID attribute)
977
+ * @param callback (optional) the result callback with two arguments:
978
+ * error: null or Error('message')
979
+ * content: the server answer that updateAt and taskID
980
+ */
981
+ partialUpdateObjects: function(objects, callback) {
982
+ var indexObj = this;
983
+ var postObj = {requests: []};
984
+ for (var i = 0; i < objects.length; ++i) {
985
+ var request = { action: 'partialUpdateObject',
986
+ objectID: objects[i].objectID,
987
+ body: objects[i] };
988
+ postObj.requests.push(request);
989
+ }
990
+ return this.as._jsonRequest({ method: 'POST',
991
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
992
+ body: postObj,
993
+ callback: callback });
994
+ },
995
+ /*
996
+ * Override the content of object
997
+ *
998
+ * @param object contains the javascript object to save, the object must contains an objectID attribute
999
+ * @param callback (optional) the result callback with two arguments:
1000
+ * error: null or Error('message')
1001
+ * content: the server answer that updateAt and taskID
1002
+ */
1003
+ saveObject: function(object, callback) {
1004
+ var indexObj = this;
1005
+ return this.as._jsonRequest({ method: 'PUT',
1006
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID),
1007
+ body: object,
1008
+ callback: callback });
1009
+ },
1010
+ /*
1011
+ * Override the content of several objects
1012
+ *
1013
+ * @param objects contains an array of objects to update (each object must contains a objectID attribute)
1014
+ * @param callback (optional) the result callback with two arguments:
1015
+ * error: null or Error('message')
1016
+ * content: the server answer that updateAt and taskID
1017
+ */
1018
+ saveObjects: function(objects, callback) {
1019
+ var indexObj = this;
1020
+ var postObj = {requests: []};
1021
+ for (var i = 0; i < objects.length; ++i) {
1022
+ var request = { action: 'updateObject',
1023
+ objectID: objects[i].objectID,
1024
+ body: objects[i] };
1025
+ postObj.requests.push(request);
1026
+ }
1027
+ return this.as._jsonRequest({ method: 'POST',
1028
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
1029
+ body: postObj,
1030
+ callback: callback });
1031
+ },
1032
+ /*
1033
+ * Delete an object from the index
1034
+ *
1035
+ * @param objectID the unique identifier of object to delete
1036
+ * @param callback (optional) the result callback with two arguments:
1037
+ * error: null or Error('message')
1038
+ * content: the server answer that contains 3 elements: createAt, taskId and objectID
1039
+ */
1040
+ deleteObject: function(objectID, callback) {
1041
+ if (typeof objectID === 'function' || typeof objectID !== 'string' && typeof objectID !== 'number') {
1042
+ var err = new Error('Cannot delete an object without an objectID');
1043
+ callback = objectID;
1044
+ if (typeof callback === 'function') {
1045
+ return callback(err);
1046
+ }
1047
+
1048
+ return this.as._request.reject(err);
1049
+ }
1050
+
1051
+ var indexObj = this;
1052
+ return this.as._jsonRequest({ method: 'DELETE',
1053
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
1054
+ callback: callback });
1055
+ },
1056
+ /*
1057
+ * Search inside the index using XMLHttpRequest request (Using a POST query to
1058
+ * minimize number of OPTIONS queries: Cross-Origin Resource Sharing).
1059
+ *
1060
+ * @param query the full text query
1061
+ * @param args (optional) if set, contains an object with query parameters:
1062
+ * - page: (integer) Pagination parameter used to select the page to retrieve.
1063
+ * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
1064
+ * - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20.
1065
+ * - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size).
1066
+ * Attributes are separated with a comma (for example "name,address").
1067
+ * You can also use an array (for example ["name","address"]).
1068
+ * By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index.
1069
+ * - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query.
1070
+ * Attributes are separated by a comma. You can also use an array (for example ["name","address"]).
1071
+ * If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted.
1072
+ * You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted.
1073
+ * A matchLevel is returned for each highlighted attribute and can contain:
1074
+ * - full: if all the query terms were found in the attribute,
1075
+ * - partial: if only some of the query terms were found,
1076
+ * - none: if none of the query terms were found.
1077
+ * - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`).
1078
+ * Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10).
1079
+ * You can also use an array (Example: attributesToSnippet: ['name:10','content:10']). By default no snippet is computed.
1080
+ * - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3.
1081
+ * - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7.
1082
+ * - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute.
1083
+ * - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma).
1084
+ * For example aroundLatLng=47.316669,5.016670).
1085
+ * You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision
1086
+ * (for example if you set aroundPrecision=100, two objects that are distant of less than 100m will be considered as identical for "geo" ranking parameter).
1087
+ * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
1088
+ * - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng).
1089
+ * For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201).
1090
+ * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
1091
+ * - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma.
1092
+ * The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`.
1093
+ * You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000.
1094
+ * You can also use an array (for example numericFilters: ["price>100","price<1000"]).
1095
+ * - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas.
1096
+ * To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3).
1097
+ * You can also use an array, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3).
1098
+ * At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}).
1099
+ * - facetFilters: filter the query by a list of facets.
1100
+ * Facets are separated by commas and each facet is encoded as `attributeName:value`.
1101
+ * For example: `facetFilters=category:Book,author:John%20Doe`.
1102
+ * You can also use an array (for example `["category:Book","author:John%20Doe"]`).
1103
+ * - facets: List of object attributes that you want to use for faceting.
1104
+ * Comma separated list: `"category,author"` or array `['category','author']`
1105
+ * Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter.
1106
+ * You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**.
1107
+ * - queryType: select how the query words are interpreted, it can be one of the following value:
1108
+ * - prefixAll: all query words are interpreted as prefixes,
1109
+ * - prefixLast: only the last word is interpreted as a prefix (default behavior),
1110
+ * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
1111
+ * - optionalWords: a string that contains the list of words that should be considered as optional when found in the query.
1112
+ * Comma separated and array are accepted.
1113
+ * - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set.
1114
+ * This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter,
1115
+ * all hits containing a duplicate value for the attributeForDistinct attribute are removed from results.
1116
+ * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best
1117
+ * one is kept and others are removed.
1118
+ * - restrictSearchableAttributes: List of attributes you want to use for textual search (must be a subset of the attributesToIndex index setting)
1119
+ * either comma separated or as an array
1120
+ * @param callback the result callback with two arguments:
1121
+ * error: null or Error('message'). If false, the content contains the error.
1122
+ * content: the server answer that contains the list of results.
1123
+ */
1124
+ search: function(query, args, callback) {
1125
+ if (arguments.length === 0 || typeof query === 'function') {
1126
+ // .search(), .search(cb)
1127
+ callback = query;
1128
+ query = '';
1129
+ } else if (arguments.length === 1 || typeof args === 'function') {
1130
+ // .search(query/args), .search(query, cb)
1131
+ callback = args;
1132
+ args = undefined;
1133
+ }
1134
+
1135
+ // .search(args), careful: typeof null === 'object'
1136
+ if (typeof query === 'object' && query !== null) {
1137
+ args = query;
1138
+ query = undefined;
1139
+ } else if (query === undefined || query === null) { // .search(undefined/null)
1140
+ query = '';
1141
+ }
1142
+
1143
+ var params = '';
1144
+
1145
+ if (query !== undefined) {
1146
+ params += 'query=' + encodeURIComponent(query);
1147
+ }
1148
+
1149
+ if (args !== undefined) {
1150
+ params = this.as._getSearchParams(args, params);
1151
+ }
1152
+
1153
+ return this._search(params, callback);
1154
+ },
1155
+
1156
+ /*
1157
+ * Browse all index content
1158
+ *
1159
+ * @param page Pagination parameter used to select the page to retrieve.
1160
+ * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
1161
+ * @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000.
1162
+ * @param callback the result callback with two arguments:
1163
+ * error: null or Error('message'). If false, the content contains the error.
1164
+ * content: the server answer that contains the list of results.
1165
+ */
1166
+ browse: function(page, hitsPerPage, callback) {
1167
+ var indexObj = this;
1168
+
1169
+ if (arguments.length === 1 || typeof hitsPerPage === 'function') {
1170
+ callback = hitsPerPage;
1171
+ hitsPerPage = undefined;
1172
+ }
1173
+
1174
+ var params = '?page=' + page;
1175
+ if (!this.as._isUndefined(hitsPerPage)) {
1176
+ params += '&hitsPerPage=' + hitsPerPage;
1177
+ }
1178
+ return this.as._jsonRequest({ method: 'GET',
1179
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params,
1180
+ callback: callback });
1181
+ },
1182
+
1183
+ /*
1184
+ * Get a Typeahead.js adapter
1185
+ * @param searchParams contains an object with query parameters (see search for details)
1186
+ */
1187
+ ttAdapter: function(params) {
1188
+ var self = this;
1189
+ return function(query, cb) {
1190
+ self.search(query, params, function(err, content) {
1191
+ if (err) {
1192
+ cb(err);
1193
+ return;
1194
+ }
1195
+
1196
+ cb(content.hits);
1197
+ });
1198
+ };
1199
+ },
1200
+
1201
+ /*
1202
+ * Wait the publication of a task on the server.
1203
+ * All server task are asynchronous and you can check with this method that the task is published.
1204
+ *
1205
+ * @param taskID the id of the task returned by server
1206
+ * @param callback the result callback with with two arguments:
1207
+ * error: null or Error('message')
1208
+ * content: the server answer that contains the list of results
1209
+ */
1210
+ waitTask: function(taskID, callback) {
1211
+ // waitTask() must be handled differently from other methods,
1212
+ // it's a recursive method using a timeout
1213
+ var indexObj = this;
1214
+
1215
+ var promise = this.as._jsonRequest({
1216
+ method: 'GET',
1217
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID
1218
+ }).then(function success(content) {
1219
+ if (content.status !== 'published') {
1220
+ return new indexObj.as._request.delay(100).then(function() {
1221
+ return indexObj.waitTask(taskID, callback);
1222
+ });
1223
+ }
1224
+
1225
+ if (callback) {
1226
+ process.nextTick(function() {
1227
+ callback(null, content);
1228
+ });
1229
+ } else {
1230
+ return content;
1231
+ }
1232
+ }, function failure(err) {
1233
+ if (callback) {
1234
+ process.nextTick(function() {
1235
+ callback(err);
1236
+ });
1237
+ } else {
1238
+ return err;
1239
+ }
1240
+ });
1241
+
1242
+ if (!callback) {
1243
+ return promise;
1244
+ }
1245
+ },
1246
+
1247
+ /*
1248
+ * This function deletes the index content. Settings and index specific API keys are kept untouched.
1249
+ *
1250
+ * @param callback (optional) the result callback with two arguments
1251
+ * error: null or Error('message')
1252
+ * content: the settings object or the error message if a failure occured
1253
+ */
1254
+ clearIndex: function(callback) {
1255
+ var indexObj = this;
1256
+ return this.as._jsonRequest({ method: 'POST',
1257
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear',
1258
+ callback: callback });
1259
+ },
1260
+ /*
1261
+ * Get settings of this index
1262
+ *
1263
+ * @param callback (optional) the result callback with two arguments
1264
+ * error: null or Error('message')
1265
+ * content: the settings object or the error message if a failure occured
1266
+ */
1267
+ getSettings: function(callback) {
1268
+ var indexObj = this;
1269
+ return this.as._jsonRequest({ method: 'GET',
1270
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
1271
+ callback: callback });
1272
+ },
1273
+
1274
+ /*
1275
+ * Set settings for this index
1276
+ *
1277
+ * @param settigns the settings object that can contains :
1278
+ * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3).
1279
+ * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7).
1280
+ * - hitsPerPage: (integer) the number of hits per page (default = 10).
1281
+ * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
1282
+ * If set to null, all attributes are retrieved.
1283
+ * - attributesToHighlight: (array of strings) default list of attributes to highlight.
1284
+ * If set to null, all indexed attributes are highlighted.
1285
+ * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords).
1286
+ * By default no snippet is computed. If set to null, no snippet is computed.
1287
+ * - attributesToIndex: (array of strings) the list of fields you want to index.
1288
+ * If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results.
1289
+ * This parameter has two important uses:
1290
+ * - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to
1291
+ * retrieve it but you don't want to search in the base64 string.
1292
+ * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of
1293
+ * the list will be considered more important than matches in attributes further down the list.
1294
+ * In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable
1295
+ * this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"].
1296
+ * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
1297
+ * All strings in the attribute selected for faceting are extracted and added as a facet. If set to null, no attribute is used for faceting.
1298
+ * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled
1299
+ * in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results.
1300
+ * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best one is kept and others are removed.
1301
+ * - ranking: (array of strings) controls the way results are sorted.
1302
+ * We have six available criteria:
1303
+ * - typo: sort according to number of typos,
1304
+ * - geo: sort according to decreassing distance when performing a geo-location based search,
1305
+ * - proximity: sort according to the proximity of query words in hits,
1306
+ * - attribute: sort according to the order of attributes defined by attributesToIndex,
1307
+ * - exact:
1308
+ * - if the user query contains one word: sort objects having an attribute that is exactly the query word before others.
1309
+ * For example if you search for the "V" TV show, you want to find it with the "V" query and avoid to have all popular TV
1310
+ * show starting by the v letter before it.
1311
+ * - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix).
1312
+ * - custom: sort according to a user defined formula set in **customRanking** attribute.
1313
+ * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
1314
+ * - customRanking: (array of strings) lets you specify part of the ranking.
1315
+ * The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator.
1316
+ * For example `"customRanking" => ["desc(population)", "asc(name)"]`
1317
+ * - queryType: Select how the query words are interpreted, it can be one of the following value:
1318
+ * - prefixAll: all query words are interpreted as prefixes,
1319
+ * - prefixLast: only the last word is interpreted as a prefix (default behavior),
1320
+ * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
1321
+ * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to "<em>").
1322
+ * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to "</em>").
1323
+ * - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query.
1324
+ * @param callback (optional) the result callback with two arguments
1325
+ * error: null or Error('message')
1326
+ * content: the server answer or the error message if a failure occured
1327
+ */
1328
+ setSettings: function(settings, callback) {
1329
+ var indexObj = this;
1330
+ return this.as._jsonRequest({ method: 'PUT',
1331
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
1332
+ body: settings,
1333
+ callback: callback });
1334
+ },
1335
+ /*
1336
+ * List all existing user keys associated to this index
1337
+ *
1338
+ * @param callback the result callback with two arguments
1339
+ * error: null or Error('message')
1340
+ * content: the server answer with user keys list
1341
+ */
1342
+ listUserKeys: function(callback) {
1343
+ var indexObj = this;
1344
+ return this.as._jsonRequest({ method: 'GET',
1345
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1346
+ callback: callback });
1347
+ },
1348
+ /*
1349
+ * Get ACL of a user key associated to this index
1350
+ *
1351
+ * @param key
1352
+ * @param callback the result callback with two arguments
1353
+ * error: null or Error('message')
1354
+ * content: the server answer with user keys list
1355
+ */
1356
+ getUserKeyACL: function(key, callback) {
1357
+ var indexObj = this;
1358
+ return this.as._jsonRequest({ method: 'GET',
1359
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
1360
+ callback: callback });
1361
+ },
1362
+ /*
1363
+ * Delete an existing user key associated to this index
1364
+ *
1365
+ * @param key
1366
+ * @param callback the result callback with two arguments
1367
+ * error: null or Error('message')
1368
+ * content: the server answer with user keys list
1369
+ */
1370
+ deleteUserKey: function(key, callback) {
1371
+ var indexObj = this;
1372
+ return this.as._jsonRequest({ method: 'DELETE',
1373
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
1374
+ callback: callback });
1375
+ },
1376
+ /*
1377
+ * Add an existing user key associated to this index
1378
+ *
1379
+ * @param acls the list of ACL for this key. Defined by an array of strings that
1380
+ * can contains the following values:
1381
+ * - search: allow to search (https and http)
1382
+ * - addObject: allows to add/update an object in the index (https only)
1383
+ * - deleteObject : allows to delete an existing object (https only)
1384
+ * - deleteIndex : allows to delete index content (https only)
1385
+ * - settings : allows to get index settings (https only)
1386
+ * - editSettings : allows to change index settings (https only)
1387
+ * @param callback the result callback with two arguments
1388
+ * error: null or Error('message')
1389
+ * content: the server answer with user keys list
1390
+ */
1391
+ addUserKey: function(acls, callback) {
1392
+ var indexObj = this;
1393
+ var aclsObject = {};
1394
+ aclsObject.acl = acls;
1395
+ return this.as._jsonRequest({ method: 'POST',
1396
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1397
+ body: aclsObject,
1398
+ callback: callback });
1399
+ },
1400
+ /*
1401
+ * Add an existing user key associated to this index
1402
+ *
1403
+ * @param acls the list of ACL for this key. Defined by an array of strings that
1404
+ * can contains the following values:
1405
+ * - search: allow to search (https and http)
1406
+ * - addObject: allows to add/update an object in the index (https only)
1407
+ * - deleteObject : allows to delete an existing object (https only)
1408
+ * - deleteIndex : allows to delete index content (https only)
1409
+ * - settings : allows to get index settings (https only)
1410
+ * - editSettings : allows to change index settings (https only)
1411
+ * @param params.validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
1412
+ * @param params.maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
1413
+ * @param params.maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
1414
+ * @param callback the result callback with two arguments
1415
+ * error: null or Error('message')
1416
+ * content: the server answer with user keys list
1417
+ */
1418
+ addUserKeyWithValidity: function(acls, params, callback) {
1419
+ var indexObj = this;
1420
+ var aclsObject = {};
1421
+ aclsObject.acl = acls;
1422
+ aclsObject.validity = params.validity;
1423
+ aclsObject.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour;
1424
+ aclsObject.maxHitsPerQuery = params.maxHitsPerQuery;
1425
+ return this.as._jsonRequest({ method: 'POST',
1426
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1427
+ body: aclsObject,
1428
+ callback: callback });
1429
+ },
1430
+ ///
1431
+ /// Internal methods only after this line
1432
+ ///
1433
+ _search: function(params, callback) {
1434
+ return this.as._jsonRequest({ cache: this.cache,
1435
+ method: 'POST',
1436
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query',
1437
+ body: {params: params},
1438
+ fallback: {
1439
+ method: 'GET',
1440
+ url: '/1/indexes/' + encodeURIComponent(this.indexName),
1441
+ body: {params: params}
1442
+ },
1443
+ callback: callback
1444
+ });
1445
+ },
1446
+
1447
+ // internal attributes
1448
+ as: null,
1449
+ indexName: null,
1450
+ typeAheadArgs: null,
1451
+ typeAheadValueOption: null
1452
+ };
1453
+
1454
+ // extracted from https://github.com/component/map/blob/master/index.js
1455
+ // without the crazy toFunction thing
1456
+ function map(arr, fn){
1457
+ var ret = [];
1458
+ for (var i = 0; i < arr.length; ++i) {
1459
+ ret.push(fn(arr[i], i));
1460
+ }
1461
+ return ret;
1462
+ }
1463
+
1464
+ // extracted from https://github.com/coolaj86/knuth-shuffle
1465
+ // not compatible with browserify
1466
+ function shuffle(array) {
1467
+ /*eslint-disable*/
1468
+ var currentIndex = array.length
1469
+ , temporaryValue
1470
+ , randomIndex
1471
+ ;
1472
+
1473
+ // While there remain elements to shuffle...
1474
+ while (0 !== currentIndex) {
1475
+
1476
+ // Pick a remaining element...
1477
+ randomIndex = Math.floor(Math.random() * currentIndex);
1478
+ currentIndex -= 1;
1479
+
1480
+ // And swap it with the current element.
1481
+ temporaryValue = array[currentIndex];
1482
+ array[currentIndex] = array[randomIndex];
1483
+ array[randomIndex] = temporaryValue;
1484
+ }
1485
+
1486
+ return array;
1487
+ }
1488
+
1489
+ }).call(this,require(1))
1490
+ },{"1":1}],3:[function(require,module,exports){
1491
+ (function (global){
1492
+ var createAlgoliasearch = require(5);
1493
+ var JSONPRequest = require(4);
1494
+
1495
+ global.angular.module('algoliasearch', [])
1496
+ .service('algolia', ['$http', '$q', '$timeout', function ($http, $q, $timeout) {
1497
+ function request(url, opts) {
1498
+ return $q(function(resolve, reject) {
1499
+ var timedOut;
1500
+ var body = null;
1501
+
1502
+ if (opts.body !== undefined) {
1503
+ body = JSON.stringify(opts.body);
1504
+ }
1505
+
1506
+ var timeout = $q(function(resolveTimeout) {
1507
+ $timeout(function() {
1508
+ timedOut = true;
1509
+ // will cancel the xhr
1510
+ resolveTimeout('test');
1511
+ resolve(new Error('Timeout - Could not connect to endpoint ' + url));
1512
+ }, opts.timeout);
1513
+ });
1514
+
1515
+ $http({
1516
+ url: url,
1517
+ method: opts.method,
1518
+ data: body,
1519
+ cache: false,
1520
+ timeout: timeout
1521
+ }).then(function success(response) {
1522
+ resolve({
1523
+ statusCode: response.status,
1524
+ body: response.data
1525
+ });
1526
+ }, function error(response) {
1527
+ if (timedOut) {
1528
+ return;
1529
+ }
1530
+
1531
+ // network error
1532
+ if (response.status === 0) {
1533
+ reject(new Error('Network error'));
1534
+ return;
1535
+ }
1536
+
1537
+ resolve({
1538
+ body: response.data,
1539
+ statusCode: response.status
1540
+ });
1541
+ });
1542
+ });
1543
+ }
1544
+
1545
+ request.fallback = function(url, opts) {
1546
+ return $q(function(resolve, reject) {
1547
+ JSONPRequest(url, opts, function JSONPRequestDone(err, content) {
1548
+ if (err) {
1549
+ reject(err);
1550
+ return;
1551
+ }
1552
+
1553
+ resolve(content);
1554
+ });
1555
+ });
1556
+ };
1557
+
1558
+ request.reject = function(val) {
1559
+ return $q.reject(val);
1560
+ };
1561
+
1562
+ request.resolve = function(val) {
1563
+ // http://www.bennadel.com/blog/2735-q-when-is-the-missing-q-resolve-method-in-angularjs.htm
1564
+ return $q.when(val);
1565
+ };
1566
+
1567
+ request.delay = function(ms) {
1568
+ return $q(function(resolve/*, reject*/) {
1569
+ $timeout(resolve, ms);
1570
+ });
1571
+ };
1572
+
1573
+ var algoliasearch = createAlgoliasearch(request);
1574
+ return {
1575
+ Client: function(applicationID, apiKey, options) {
1576
+ return algoliasearch(applicationID, apiKey, options);
1577
+ }
1578
+ };
1579
+ }]);
1580
+
1581
+ }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
1582
+ },{"4":4,"5":5}],4:[function(require,module,exports){
1583
+ module.exports = JSONPRequest;
1584
+
1585
+ var JSONPCounter = 0;
1586
+
1587
+ function JSONPRequest(url, opts, cb) {
1588
+ if (opts.method !== 'GET') {
1589
+ cb(new Error('Method ' + opts.method + ' ' + url + ' is not supported by JSONP.'));
1590
+ return;
1591
+ }
1592
+
1593
+ var cbCalled = false;
1594
+ var timedOut = false;
1595
+
1596
+ JSONPCounter += 1;
1597
+ var head = document.getElementsByTagName('head')[0];
1598
+ var script = document.createElement('script');
1599
+ var cbName = 'algoliaJSONP_' + JSONPCounter;
1600
+ var done = false;
1601
+
1602
+ window[cbName] = function(data) {
1603
+ try {
1604
+ delete window[cbName];
1605
+ } catch (e) {
1606
+ window[cbName] = undefined;
1607
+ }
1608
+
1609
+ if (timedOut) {
1610
+ return;
1611
+ }
1612
+
1613
+ cbCalled = true;
1614
+
1615
+ clean();
1616
+
1617
+ cb(null, {
1618
+ body: data/*,
1619
+ // We do not send the statusCode, there's no statusCode in JSONP, it will be
1620
+ // computed using data.status && data.message like with XDR
1621
+ statusCode*/
1622
+ });
1623
+ };
1624
+
1625
+ // add callback by hand
1626
+ url += '&callback=' + cbName;
1627
+
1628
+ // add body params by hand
1629
+ if (opts.body && opts.body.params) {
1630
+ url += '&' + opts.body.params;
1631
+ }
1632
+
1633
+ var ontimeout = setTimeout(timeout, opts.timeout);
1634
+
1635
+ // script onreadystatechange needed only for
1636
+ // <= IE8
1637
+ // https://github.com/angular/angular.js/issues/4523
1638
+ script.onreadystatechange = readystatechange;
1639
+ script.onload = success;
1640
+ script.onerror = error;
1641
+
1642
+ script.async = true;
1643
+ script.defer = true;
1644
+ script.src = url;
1645
+ head.appendChild(script);
1646
+
1647
+ function success() {
1648
+ if (done || timedOut) {
1649
+ return;
1650
+ }
1651
+
1652
+ done = true;
1653
+
1654
+ // script loaded but did not call the fn => script loading error
1655
+ if (!cbCalled) {
1656
+ clean();
1657
+ cb(new Error('Failed to load JSONP script'));
1658
+ }
1659
+ }
1660
+
1661
+ function readystatechange() {
1662
+ if (this.readyState === 'loaded' || this.readyState === 'complete') {
1663
+ success();
1664
+ }
1665
+ }
1666
+
1667
+ function clean() {
1668
+ clearTimeout(ontimeout);
1669
+ script.onload = null;
1670
+ script.onreadystatechange = null;
1671
+ script.onerror = null;
1672
+ head.removeChild(script);
1673
+
1674
+ try {
1675
+ delete window[cbName];
1676
+ delete window[cbName + '_loaded'];
1677
+ } catch (e) {
1678
+ window[cbName] = null;
1679
+ window[cbName + '_loaded'] = null;
1680
+ }
1681
+ }
1682
+
1683
+ function timeout() {
1684
+ timedOut = true;
1685
+ clean();
1686
+ cb(new Error('Timeout - Could not connect to endpoint ' + url));
1687
+ }
1688
+
1689
+ function error() {
1690
+ if (done || timedOut) {
1691
+ return;
1692
+ }
1693
+
1694
+ clean();
1695
+ cb(new Error('Failed to load JSONP script'));
1696
+ }
1697
+ }
1698
+
1699
+ },{}],5:[function(require,module,exports){
1700
+ // this file is a `factory of algoliasearch()`
1701
+ // Given a `request` param, it will provide you an AlgoliaSearch client
1702
+ // using this particular request
1703
+ module.exports = createAlgoliasearch;
1704
+
1705
+ function createAlgoliasearch(request) {
1706
+ function algoliasearch(applicationID, apiKey, opts) {
1707
+ var AlgoliaSearch = require(2);
1708
+
1709
+ return new AlgoliaSearch(applicationID, apiKey, opts, request);
1710
+ }
1711
+
1712
+ algoliasearch.version = "3.0.3";
1713
+
1714
+ return algoliasearch;
1715
+ }
1716
+
1717
+ },{"2":2}]},{},[3]);