algoliasearch
Advanced tools
Comparing version 2.9.4 to 3.0.0
{ | ||
"name": "algoliasearch", | ||
"version": "2.9.4", | ||
"version": "3.0.0", | ||
"homepage": "https://github.com/algolia/algoliasearch-client-js", | ||
@@ -19,10 +19,7 @@ "authors": [ | ||
"ignore": [ | ||
"**/.*", | ||
"example", | ||
"examples", | ||
"node_modules", | ||
"bower_components", | ||
"test", | ||
"tests", | ||
"vendor" | ||
"test" | ||
] | ||
} |
{ | ||
"name": "algoliasearch", | ||
"description": "Algolia Search API Client", | ||
"version": "3.0.0", | ||
"description": "AlgoliaSearch API JavaScript client", | ||
"main": "index.js", | ||
"browser": "src/browser.js", | ||
"scripts": { | ||
"build": "PACKAGE_VERSION=$(json -f package.json version) sh build.sh", | ||
"test": "zuul --phantom --ui tape test/run.js | tap-growl | tap-spec && npm run lint", | ||
"test-ci": "DEBUG=zuul* zuul --tunnel ngrok test/run.js && npm run lint", | ||
"dev": "DEBUG=zuul* zuul --no-coverage --local 8080 --ui tape test/run.js", | ||
"examples": "http-server . -a 0.0.0.0", | ||
"lint": "eslint --quiet test/" | ||
}, | ||
"browserify": { | ||
"transform": [ | ||
"packageify" | ||
] | ||
}, | ||
"repository": { | ||
@@ -28,23 +44,27 @@ "type": "git", | ||
], | ||
"dependencies": { | ||
"es6-promise": "2.0.1" | ||
}, | ||
"devDependencies": { | ||
"angular": "1.3.14", | ||
"bowser": "0.7.2", | ||
"browserify": "9.0.3", | ||
"browserify-shim": "3.8.3", | ||
"bulk-require": "0.2.1", | ||
"bulkify": "1.1.1", | ||
"bundle-collapser": "1.1.4", | ||
"chance": "0.7.3", | ||
"closurecompiler": "1.5.1", | ||
"compression": "1.4.3", | ||
"domready": "0.3.0", | ||
"eslint": "0.15.0", | ||
"express": "4.12.1", | ||
"faux-jax": "2.0.0", | ||
"grunt": "0.4.5", | ||
"grunt-cli": "^0.1.13", | ||
"grunt-contrib-clean": "0.6.0", | ||
"grunt-contrib-concat": "0.5.1", | ||
"grunt-contrib-jshint": "0.11.0", | ||
"grunt-contrib-uglify": "0.8.0", | ||
"grunt-sed": "0.1.1", | ||
"faux-jax": "3.0.1", | ||
"http-server": "0.7.5", | ||
"lodash": "3.3.1", | ||
"lodash-compat": "3.3.1", | ||
"jQuery-ajaxTransport-XDomainRequest": "git://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest#1.0.4", | ||
"jquery": "2.1.3", | ||
"json": "9.0.3", | ||
"lodash": "3.5.0", | ||
"lodash-compat": "3.5.0", | ||
"morgan": "1.5.1", | ||
"packageify": "0.2.2", | ||
"phantomjs": "1.9.15", | ||
@@ -56,27 +76,6 @@ "sinon": "1.12.2", | ||
"url-parse": "1.0.0", | ||
"writable-window-method": "1.0.0", | ||
"xhr": "2.0.1", | ||
"zuul": "2.1.0", | ||
"zuul-ngrok": "git://github.com/vvo/zuul-ngrok#decbe165e0a50b7afaef3eb1dd1f49ed711bc8d7" | ||
}, | ||
"browser": { | ||
"algoliasearch": "./dist/algoliasearch.js" | ||
}, | ||
"browserify": { | ||
"transform": [ | ||
"browserify-shim" | ||
] | ||
}, | ||
"browserify-shim": { | ||
"algoliasearch": "AlgoliaSearch" | ||
}, | ||
"scripts": { | ||
"test": "grunt build && zuul --phantom --ui tape test/run.js | tap-growl | tap-spec && npm run lint", | ||
"test-ci": "grunt build && DEBUG=zuul* zuul --tunnel ngrok test/run.js && npm run lint", | ||
"dev": "grunt build && DEBUG=zuul* zuul --local 8080 --ui tape test/run.js", | ||
"examples": "http-server . -a 127.0.0.1", | ||
"lint": "eslint --quiet test/" | ||
}, | ||
"version": "2.9.4", | ||
"dependencies": {} | ||
"zuul": "2.1.1", | ||
"zuul-ngrok": "3.0.0" | ||
} | ||
} |
426
README.md
@@ -6,18 +6,5 @@ # Algolia Search API Client for JavaScript | ||
[Algolia Search](http://www.algolia.com) is a hosted full-text, numerical, and faceted search engine capable of delivering realtime results from the first keystroke. | ||
Algolia's Search API makes it easy to deliver a great search experience in your websites and mobile applications by providing: | ||
* REST and JSON based API | ||
* Search against infinite attributes from a single search box | ||
* Instant search as you type experience | ||
* Relevance and popularity ranking | ||
* Global language support | ||
* Typo tolerance in any language | ||
* Smart highlighting | ||
* Facet as you type | ||
* Geo awareness | ||
* 99.99% SLA | ||
* First class data security | ||
[![Version][version-svg]][package-url] [![Build Status][travis-svg]][travis-url] [![License][license-image]][license-url] [![Downloads][downloads-image]][downloads-url] [![Libscore][libscore-svg]][libscore-url] | ||
@@ -40,13 +27,13 @@ | ||
Our JavaScript client lets you easily use the [Algolia Search API](http://www.algolia.com) in a browser. | ||
The JavaScript client lets you easily use the [Algolia Search API](https://www.algolia.com/doc/rest_api) in a browser. | ||
It works and has been tested in all the major browsers. | ||
It is dedicated to web apps searching directly from the browser. | ||
To add, remove or delete your objects please consider using [a backend API client](https://www.algolia.com/doc). | ||
Our JavaScript client uses either: | ||
Our JavaScript library is [UMD](https://github.com/umdjs/umd) compatible, you can | ||
use it with any module loader. | ||
- [CORS](http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing#Browser_support) for modern browsers | ||
- [XDomainRequest](https://msdn.microsoft.com/en-us/library/ie/cc288060%28v=vs.85%29.aspx) for IE <= 10 | ||
- [JSONP](http://en.wikipedia.org/wiki/JSONP) in any situation where Ajax requests are unavailabe or blocked. | ||
When not using any module loader, it will export an `alogliasearch` method in the `window` object. | ||
The JavaScript API client is dedicated to web apps searching directly from the browser. To add, remove or delete your objects please consider using a backend API client. | ||
If you are using the V2 of our JavaScript client and want to upgrade, please read [our migration guide](https://github.com/algolia/algoliasearch-client-js/wiki/Migration-guide-from-2.x.x-to-3.x.x). | ||
@@ -62,2 +49,4 @@ | ||
1. [Callback convention](#callback-convention) | ||
1. [Promises](#promises) | ||
1. [Request strategy](#request-strategy) | ||
1. [Cache](#cache) | ||
@@ -71,3 +60,3 @@ 1. [Online documentation](#documentation) | ||
1. [Search](#search) | ||
1. [Multiple queries](#multi-queries) | ||
1. [Multiple queries](#multiple-queries) | ||
1. [Get an object](#get-an-object) | ||
@@ -86,61 +75,64 @@ 1. [Security](#security) | ||
#### Bower | ||
#### npm | ||
```sh | ||
bower install algoliasearch | ||
npm install alogliasearch --save | ||
``` | ||
#### jsDelivr, cdnjs | ||
We are [browserify](http://browserify.org/)able. | ||
Both [jsDelivr](http://www.jsdelivr.com/about.php) and [cdnjs](https://cdnjs.com/about) are | ||
offering global CDN delivery for our JavaScript client. | ||
#### Bower | ||
```html | ||
<script src="//cdn.jsdelivr.net/algoliasearch/{VERSION}/algoliasearch.min.js"></script> | ||
```sh | ||
bower install algoliasearch | ||
``` | ||
```html | ||
<script src="//cdnjs.cloudflare.com/ajax/libs/algoliasearch/{VERSION}/algoliasearch.min.js"></script> | ||
``` | ||
#### jsDelivr | ||
Please do not use `latest` as the {VERSION} tag, you can find the latest tag. | ||
[jsDelivr](http://www.jsdelivr.com/about.php) is a global CDN delivery for JavaScript libraries. | ||
The current stable version of our JavaScript client is [![Version][version-svg]][package-url] | ||
You can always get the latest, backward compatible version by including: | ||
#### jQuery, Angular.js | ||
We have specific builds for [jQuery](http://jquery.com/) and [Angular.js](https://angularjs.org/). | ||
```html | ||
<script src="//cdn.jsdelivr.net/algoliasearch/{VERSION}/algoliasearch.jquery.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script> | ||
``` | ||
```html | ||
<script src="//cdn.jsdelivr.net/algoliasearch/{VERSION}/algoliasearch.angular.min.js"></script> | ||
``` | ||
### Example | ||
```html | ||
<script src="http://domain/path-to/algoliasearch.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script> | ||
<script> | ||
var client = new AlgoliaSearch('ApplicationID', 'Search-Only-API-Key'); | ||
var client = algoliasearch('ApplicationID', 'Search-Only-API-Key'); | ||
var index = client.initIndex('indexName'); | ||
index.search('something', function(success, hits) { | ||
console.log(success, hits) | ||
index.search('an example', function searchDone(err, content) { | ||
console.log(err, content) | ||
}); | ||
index.search('another example') | ||
.then(function searchSuccess(content) { | ||
console.log(content); | ||
}) | ||
.catch(function searchFailure(err) { | ||
console.error(err); | ||
}); | ||
</script> | ||
``` | ||
Have a look at our [callback convention](#callback-convention), read about [our promises](#promises). | ||
#### jQuery | ||
You must have jQuery loaded in your page. | ||
We provide a specific [jQuery](http://jquery.com/) build that will use [jQuery.ajax](http://api.jquery.com/jquery.ajax/). | ||
It can be used with callbacks or [jQuery promises](https://api.jquery.com/promise/). | ||
```html | ||
<script src="http://domain/path/to/algoliasearch.jquery.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/jquery/2.1.3/jquery.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/algoliasearch/3/algoliasearch.jquery.min.js"></script> | ||
<script> | ||
var client = $.algolia.Client('ApplicationID', 'Search-Only-API-Key'); | ||
var index = client.initIndex('indexName'); | ||
index.search('something', function(success, hits) { | ||
console.log(success, hits) | ||
index.search('something', function searchDone(err, content) { | ||
console.log(err, content) | ||
}); | ||
@@ -152,6 +144,9 @@ </script> | ||
You must have Angular loaded in your page. | ||
We provide a specific [AngularJS](https://angularjs.org/) build that is using the [$http service](https://docs.angularjs.org/api/ng/service/$http). | ||
It can be used with callbacks or [AngularJS promises](https://docs.angularjs.org/api/ng/service/$q). | ||
```html | ||
<script src="http://domain/path/to/algoliasearch.angular.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/angularjs/1.3.14/angular.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/algoliasearch/3/algoliasearch.angular.min.js"></script> | ||
<script> | ||
@@ -164,4 +159,9 @@ angular | ||
var client = algolia.Client('ApplicationID', 'Search-Only-API-Key'); | ||
// ... | ||
var index = client.initIndex('indexName'); | ||
index.search('something') | ||
.then(function searchSuccess(content) { | ||
console.log(content); | ||
}, function searchFailure(err) { | ||
console.log(err); | ||
}); | ||
}]); | ||
@@ -171,2 +171,17 @@ </script> | ||
#### Browserify | ||
```sh | ||
npm install algoliasearch --save | ||
``` | ||
```js | ||
var algoliasearch = require('algoliasearch'); | ||
var client = algoliasearch('applicationID', 'Search-Only-API-Key'); | ||
var index = client.initIndex('indexName'); | ||
index.search('something', function searchDone(err, content) { | ||
console.log(err, content); | ||
}); | ||
``` | ||
We also provide [runnable examples](#quick-start) for you to try. | ||
@@ -180,2 +195,5 @@ | ||
Quick Start | ||
@@ -201,3 +219,3 @@ ------------- | ||
```js | ||
var client = new AlgoliaSearch(ApplicationID, Search-Only-API-Key); | ||
var client = algoliasearch('ApplicationID', 'Search-Only-API-Key'); | ||
var index = client.initIndex(indexName); | ||
@@ -208,2 +226,4 @@ ``` | ||
Callback convention | ||
@@ -214,8 +234,24 @@ ------------- | ||
1. **success**: a boolean that is set to false when an error occurs. | ||
2. **content**: the object containing the answer (if an error was found, you can retrieve the error message in `content.message`) | ||
1. **error**: null or an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object. More info on the error can be find in `error.message`. | ||
2. **content**: the object containing the answer | ||
We follow the [error-first callback](http://thenodeway.io/posts/understanding-error-first-callbacks/). | ||
Promises | ||
------------- | ||
If **you do not provide a callback**, you will get a promise (but never both). | ||
Promises are the [native Promise implementation](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise). | ||
We use [jakearchibald/es6-promise](https://github.com/jakearchibald/es6-promise/) as a polyfill when needed. | ||
Request strategy | ||
------------- | ||
The request strategy used by the JavaScript client includes: | ||
- [CORS](http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing#Browser_support) for modern browsers | ||
- [XDomainRequest](https://msdn.microsoft.com/en-us/library/ie/cc288060%28v=vs.85%29.aspx) for IE <= 10 | ||
- [JSONP](http://en.wikipedia.org/wiki/JSONP) in any situation where Ajax requests are unavailabe or blocked. | ||
Cache | ||
@@ -233,3 +269,3 @@ ------------- | ||
// you'll need to use the following code | ||
algoliaClient.clearCache(); | ||
client.clearCache(); | ||
``` | ||
@@ -356,74 +392,40 @@ | ||
##### Default | ||
```javascript | ||
var client = algoliasearch('ApplicationID', 'Search-Only-API-Key'); | ||
var index = client.initIndex('indexName'); | ||
```javascript | ||
index = client.initIndex('contacts'); | ||
index.search('query string', function(success, content) { | ||
if (!success) { | ||
console.log('Error: ' + content.message); | ||
return; | ||
} | ||
for (var h in content.hits) { | ||
console.log('Hit(' + content.hits[h].objectID + '): ' + content.hits[h].toString()); | ||
} | ||
// only query string | ||
index.search('query string', function searchDone(err, content) { | ||
if (err) { | ||
console.error(err); | ||
return; | ||
} | ||
for (var h in content.hits) { | ||
console.log('Hit(' + content.hits[h].objectID + '): ' + content.hits[h].toString()); | ||
} | ||
}); | ||
index.search('query string', function(success, content) { | ||
if (!success) { | ||
console.log('Error: ' + content.message); | ||
return; | ||
// with params | ||
index.search( | ||
'query string', { | ||
attributesToRetrieve: ['firstname', 'lastname'], | ||
hitsPerPage: 50 | ||
}, | ||
function searchDone(err, content) { | ||
if (err) { | ||
console.error(err); | ||
return; | ||
} | ||
for (var h in content.hits) { | ||
console.log('Hit(' + content.hits[h].objectID + '): ' + content.hits[h].toString()); | ||
console.log('Hit(' + content.hits[h].objectID + '): ' + content.hits[h].toString()); | ||
} | ||
}, {attributesToRetrieve: 'firstname,lastname', hitsPerPage: 50}); | ||
} | ||
); | ||
``` | ||
##### jQuery | ||
```javascript | ||
index = client.initIndex('contacts'); | ||
index.search('query string') | ||
.done(function(content) { | ||
for (var h in content.hits) { | ||
console.log('Hit(' + content.hits[h].objectID + '): ' + content.hits[h].toString()); | ||
} | ||
}) | ||
.fail(function(content) { | ||
console.log('Error: ' + content.message); | ||
}); | ||
index.search('query string', { attributesToRetrieve: 'firstname,lastname', hitsPerPage: 50}) | ||
.done(function(content) { | ||
for (var h in content.hits) { | ||
console.log('Hit(' + content.hits[h].objectID + '): ' + content.hits[h].toString()); | ||
} | ||
}) | ||
.fail(function(content) { | ||
console.log('Error: ' + content.message); | ||
}); | ||
``` | ||
##### Angular.js | ||
```javascript | ||
index = client.initIndex('contacts'); | ||
index.search('query string') | ||
.then(function(content) { | ||
$scope.hits = content.hits; | ||
}, function(content) { | ||
console.log('Error: ' + content.message); | ||
}); | ||
index.search('query string', { attributesToRetrieve: 'firstname,lastname', hitsPerPage: 50}) | ||
.then(function(content) { | ||
$scope.hits = content.hits; | ||
}, function(content) { | ||
console.log('Error: ' + content.message); | ||
}); | ||
``` | ||
The server response will look like: | ||
```javascript | ||
```json | ||
{ | ||
@@ -467,5 +469,5 @@ "hits": [ | ||
#### Default | ||
```javascript | ||
var client = algoliasearch('ApplicationID', 'Search-Only-API-Key'); | ||
```javascript | ||
// perform 3 queries in a single API call: | ||
@@ -475,66 +477,33 @@ // - 1st query targets index `categories` | ||
client.startQueriesBatch(); | ||
client.addQueryInBatch('categories', $('#q').val(), { hitsPerPage: 3 }); | ||
client.addQueryInBatch('products', $('#q').val(), { hitsPerPage: 3, tagFilters: 'promotion' }); | ||
client.addQueryInBatch('products', $('#q').val(), { hitsPerPage: 10 }); | ||
client.sendQueriesBatch(searchMultiCallback); | ||
function searchMultiCallback(success, content) { | ||
if (success) { | ||
var categories = content.results[0]; | ||
for (var i = 0; i < categories.hits.length; ++i) { | ||
console.log(categories.hits[i]); | ||
} | ||
var products_promotion = content.results[1]; | ||
for (var i = 0; i < products_promotion.hits.length; ++i) { | ||
console.log(products_promotion.hits[i]); | ||
} | ||
var products = content.results[2]; | ||
for (var i = 0; i < products.hits.length; ++i) { | ||
console.log(products.hits[i]); | ||
} | ||
client.addQueryInBatch( | ||
'categories', // index name | ||
'search in categories index', { | ||
hitsPerPage: 3 | ||
} | ||
} | ||
``` | ||
); | ||
#### jQuery | ||
```javascript | ||
// perform 3 queries in a single API call: | ||
// - 1st query targets index `categories` | ||
// - 2nd and 3rd queries target index `products` | ||
client.startQueriesBatch(); | ||
client.addQueryInBatch('categories', $('#q').val(), { hitsPerPage: 3 }); | ||
client.addQueryInBatch('products', $('#q').val(), { hitsPerPage: 3, tagFilters: 'promotion' }); | ||
client.addQueryInBatch('products', $('#q').val(), { hitsPerPage: 10 }); | ||
client.sendQueriesBatch().done(function(content) { | ||
var categories = content.results[0]; | ||
for (var i = 0; i < categories.hits.length; ++i) { | ||
console.log(categories.hits[i]); | ||
client.addQueryInBatch( | ||
'products', | ||
'first search in products', { | ||
hitsPerPage: 3, | ||
tagFilters: 'promotion' | ||
} | ||
); | ||
var products_promotion = content.results[1]; | ||
for (var i = 0; i < products_promotion.hits.length; ++i) { | ||
console.log(products_promotion.hits[i]); | ||
client.addQueryInBatch( | ||
'products', | ||
'another search in products', { | ||
hitsPerPage: 10 | ||
} | ||
); | ||
var products = content.results[2]; | ||
for (var i = 0; i < products.hits.length; ++i) { | ||
console.log(products.hits[i]); | ||
client.sendQueriesBatch(searchMultiCallback); | ||
function searchMultiCallback(err, content) { | ||
if (err) { | ||
console.error(err); | ||
return; | ||
} | ||
}); | ||
``` | ||
#### Angular.js | ||
```javascript | ||
// perform 3 queries in a single API call: | ||
// - 1st query targets index `categories` | ||
// - 2nd and 3rd queries target index `products` | ||
client.startQueriesBatch(); | ||
client.addQueryInBatch('categories', $('#q').val(), { hitsPerPage: 3 }); | ||
client.addQueryInBatch('products', $('#q').val(), { hitsPerPage: 3, tagFilters: 'promotion' }); | ||
client.addQueryInBatch('products', $('#q').val(), { hitsPerPage: 10 }); | ||
client.sendQueriesBatch().then(function(content) { | ||
var categories = content.results[0]; | ||
@@ -554,3 +523,3 @@ for (var i = 0; i < categories.hits.length; ++i) { | ||
} | ||
}); | ||
} | ||
``` | ||
@@ -565,44 +534,25 @@ | ||
##### Default | ||
```javascript | ||
var client = algoliasearch('ApplicationID', 'Search-Only-API-Key'); | ||
var index = client.initIndex('indexName'); | ||
```javascript | ||
// Retrieves all attributes | ||
index.getObject('myID', function(success, content) { | ||
index.getObject('myID', function searchDone(err, content) { | ||
if (err) { | ||
console.error(err); | ||
return; | ||
} | ||
console.log(content.objectID + ": ", content); | ||
}); | ||
// Retrieves firstname and lastname attributes | ||
index.getObject('myID', function(success, content) { | ||
console.log(content.objectID + ": ", content); | ||
}, "firstname,lastname"); | ||
// Retrieves only the firstname attribute | ||
index.getObject('myID', function(success, content) { | ||
console.log(content.objectID + ": ", content); | ||
}, "firstname"); | ||
``` | ||
##### jQuery | ||
```javascript | ||
// Retrieves all attributes | ||
index.getObject('myID').done(function(content) { | ||
console.log(content.objectID + ": ", content); | ||
}); | ||
// Retrieves firstname and lastname attributes | ||
index.getObject('myID', "firstname,lastname").done(function(content) { | ||
console.log(content.objectID + ": ", content); | ||
}); | ||
``` | ||
index.getObject('myID', ['firstname', 'lastname'], function searchDone(err, content) { | ||
if (err) { | ||
console.error(err); | ||
return; | ||
} | ||
##### Angular.js | ||
```javascript | ||
// Retrieves all attributes | ||
index.getObject('myID').then(function(content) { | ||
console.log(content.objectID + ": ", content); | ||
}); | ||
// Retrieves firstname and lastname attributes | ||
index.getObject('myID', "firstname,lastname").then(function(content) { | ||
console.log(content.objectID + ": ", content); | ||
}); | ||
``` | ||
@@ -612,44 +562,28 @@ | ||
##### Default | ||
```javascript | ||
index.getObjects(['myObj1', 'myObj2'], function(success, content) { | ||
// iterate over content | ||
}); | ||
``` | ||
##### jQuery | ||
```javascript | ||
index.getObjects(['myObj1', 'myObj2']).done(function(content) { | ||
// iterate over content | ||
}); | ||
``` | ||
##### Angular.js | ||
```javascript | ||
index.getObjects(['myObj1', 'myObj2']).then(function(content) { | ||
// iterate over content | ||
}); | ||
``` | ||
Security | ||
--------- | ||
If you're using a secured API Key (see backend client documentation), you need to set the associated `tags`: | ||
If you're using [Per-User security](https://www.algolia.com/doc#SecurityUser) keys, you need to set the associated `tags`: | ||
```javascript | ||
var algolia = new AlgoliaSearch('YourApplicationID', 'YourPublicSecuredAPIKey'); | ||
algolia.setSecurityTags('(public,user_42)'); // must be same than those used at generation-time | ||
var client = algoliasearch('ApplicationID', 'YourPublicSecuredAPIKey'); | ||
// must be the same than those used at generation-time | ||
client.setSecurityTags('(public,user_42)'); | ||
``` | ||
If you've specified a `userToken` while generating your secured API key, you must also specified it at query-time: | ||
// If you've specified a `userToken` while generating your secured API key, you must also specified it at query-time: | ||
```javascript | ||
var algolia = new AlgoliaSearch('YourApplicationID', 'YourPublicSecuredAPIKey'); | ||
algolia.setSecurityTags('(public,user_42)'); // must be the same as the ones used at generation-time | ||
algolia.setUserToken('user_42') // must be the same as the one used at generation-time | ||
var client = algoliasearch('ApplicationID', 'YourPublicSecuredAPIKey'); | ||
// must be the same as the ones used at generation-time | ||
client.setSecurityTags('(public,user_42)'); | ||
// must be the same as the one used at generation-time | ||
client.setUserToken('user_42'); | ||
``` | ||
@@ -664,9 +598,13 @@ | ||
In some use cases, such as an HTML5 mobile application, it may be necessary to perform updates to the index directly in JavaScript. Therefore, just like other languages, the JavaScript client is able to add, update & delete objects, and modify index settings. For more details about updating an index from JavaScript, take a look at the [algoliasearch.js](https://github.com/algolia/algoliasearch-client-js/blob/master/src/algoliasearch.js) source file to see details about each function. If you use the JavaScript client to update the index, you need to specify `https` as the protocol during client initialization: | ||
In some use cases, such as an HTML5 mobile application, it may be necessary to perform updates to the index directly in JavaScript. | ||
Therefore, just like other languages, the JavaScript client is able to add, update, delete objects and modify index settings. | ||
If you use the JavaScript client to update the index and if you are not on an `https:` website already, you must force the client to use `https:`: | ||
```javascript | ||
<script src="algoliasearch.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script> | ||
<script> | ||
client = new AlgoliaSearch('ApplicationID', 'API-Key', { method: 'https' }); | ||
... | ||
var client = algoliasearch('ApplicationID', 'API-Key', {protocol: 'https:'}); | ||
</script> | ||
``` | ||
@@ -673,0 +611,0 @@ |
@@ -1,13 +0,90 @@ | ||
/* global angular */ | ||
angular.module('algoliasearch', []) | ||
.service('algolia', ['$injector', function ($injector) { | ||
var createAlgoliasearch = require('./create-algoliasearch'); | ||
var JSONPRequest = require('./jsonp-request'); | ||
global.angular.module('algoliasearch', []) | ||
.service('algolia', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { | ||
function request(url, opts) { | ||
return $q(function(resolve, reject) { | ||
var timedOut; | ||
var body = null; | ||
if (opts.body !== undefined) { | ||
body = JSON.stringify(opts.body); | ||
} | ||
var timeout = $q(function(resolveTimeout) { | ||
$timeout(function() { | ||
timedOut = true; | ||
// will cancel the xhr | ||
resolveTimeout('test'); | ||
resolve(new Error('Timeout - Could not connect to endpoint ' + url)); | ||
}, opts.timeout); | ||
}); | ||
$http({ | ||
url: url, | ||
method: opts.method, | ||
data: body, | ||
cache: false, | ||
timeout: timeout | ||
}).then(function success(response) { | ||
resolve({ | ||
statusCode: response.status, | ||
body: response.data | ||
}); | ||
}, function error(response) { | ||
if (timedOut) { | ||
return; | ||
} | ||
// network error | ||
if (response.status === 0) { | ||
reject(new Error('Network error')); | ||
return; | ||
} | ||
resolve({ | ||
body: response.data, | ||
statusCode: response.status | ||
}); | ||
}); | ||
}); | ||
} | ||
request.fallback = function(url, opts) { | ||
return $q(function(resolve, reject) { | ||
JSONPRequest(url, opts, function JSONPRequestDone(err, content) { | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve(content); | ||
}); | ||
}); | ||
}; | ||
request.reject = function(val) { | ||
return $q.reject(val); | ||
}; | ||
request.resolve = function(val) { | ||
// http://www.bennadel.com/blog/2735-q-when-is-the-missing-q-resolve-method-in-angularjs.htm | ||
return $q.when(val); | ||
}; | ||
request.delay = function(ms) { | ||
return $q(function(resolve/*, reject*/) { | ||
$timeout(resolve, ms); | ||
}); | ||
}; | ||
var algoliasearch = createAlgoliasearch(request); | ||
return { | ||
Client: function(applicationID, apiKey, options) { | ||
options = options || {}; | ||
options.angular = { | ||
'$injector': $injector | ||
}; | ||
return new AlgoliaSearch(applicationID, apiKey, options); | ||
return algoliasearch(applicationID, apiKey, options); | ||
} | ||
}; | ||
}]); | ||
require('./migration-layer')('algoliasearch.angular'); |
@@ -1,13 +0,75 @@ | ||
/* global jQuery */ | ||
(function ($) { | ||
var createAlgoliasearch = require('./create-algoliasearch'); | ||
var JSONPRequest = require('./jsonp-request'); | ||
$.algolia = {}; | ||
$.algolia.Client = function(applicationID, apiKey, options) { | ||
options = options || {}; | ||
options.jQuery = { | ||
'$': $ | ||
}; | ||
return new AlgoliaSearch(applicationID, apiKey, options); | ||
}; | ||
var algoliasearch = createAlgoliasearch(request); | ||
var $ = global.jQuery; | ||
}(jQuery)); | ||
$.algolia = {Client: algoliasearch}; | ||
function request(url, opts) { | ||
return $.Deferred(function(deferred) { | ||
var body = null; | ||
if (opts.body !== undefined) { | ||
body = JSON.stringify(opts.body); | ||
} | ||
$.ajax(url, { | ||
type: opts.method, | ||
timeout: opts.timeout, | ||
dataType: 'json', | ||
data: body, | ||
complete: function(jqXHR, textStatus/* , error*/) { | ||
if (textStatus === 'timeout') { | ||
deferred.resolve(new Error('Timeout - Could not connect to endpoint ' + url)); | ||
return; | ||
} | ||
if (jqXHR.status === 0) { | ||
deferred.reject(new Error('Network error')); | ||
return; | ||
} | ||
deferred.resolve({ | ||
statusCode: jqXHR.status, | ||
body: jqXHR.responseJSON | ||
}); | ||
} | ||
}); | ||
}).promise(); | ||
} | ||
request.fallback = function(url, opts) { | ||
return $.Deferred(function(deferred) { | ||
JSONPRequest(url, opts, function JSONPRequestDone(err, content) { | ||
if (err) { | ||
deferred.reject(err); | ||
return; | ||
} | ||
deferred.resolve(content); | ||
}); | ||
}).promise(); | ||
}; | ||
request.reject = function(val) { | ||
return $.Deferred(function(deferred) { | ||
deferred.reject(val); | ||
}).promise(); | ||
}; | ||
request.resolve = function(val) { | ||
return $.Deferred(function(deferred) { | ||
deferred.resolve(val); | ||
}).promise(); | ||
}; | ||
request.delay = function(ms) { | ||
return $.Deferred(function(deferred) { | ||
setTimeout(function() { | ||
deferred.resolve(); | ||
}, ms); | ||
}).promise(); | ||
}; | ||
require('./migration-layer')('algoliasearch.jquery'); |
@@ -1,203 +0,84 @@ | ||
/* | ||
* Copyright (c) 2013 Algolia | ||
* http://www.algolia.com/ | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
module.exports = AlgoliaSearch; | ||
/* | ||
* Algolia Search library initialization | ||
* @param applicationID the application ID you have in your admin interface | ||
* @param apiKey a valid API key for the service | ||
* @param methodOrOptions the hash of parameters for initialization. It can contains: | ||
* - method (optional) specify if the protocol used is http or https (http by default to make the first search query faster). | ||
* You need to use https is you are doing something else than just search queries. | ||
* - hosts (optional) the list of hosts that you have received for the service | ||
* - dsn (optional) set to true if your account has the Distributed Search Option | ||
* - dsnHost (optional) override the automatic computation of dsn hostname | ||
* https://www.algolia.com/ | ||
* | ||
* @param {string} applicationID - Your applicationID, found in your dashboard | ||
* @param {string} apiKey - Your API key, found in your dashboard | ||
* @param {Object} [opts] | ||
* @param {number} [opts.timeout=2000] - The request timeout set in milliseconds, another request will be issued after this timeout | ||
* @param {string} [opts.protocol='http:'] - The protocol used to query Algolia Search API. | ||
* Set to 'https:' to force using https. Default to document.location.protocol in browsers | ||
* @param {string[]} [opts.hosts=[ | ||
* this.applicationID + '-1.algolia.' + opts.tld, | ||
* this.applicationID + '-2.algolia.' + opts.tld, | ||
* this.applicationID + '-3.algolia.' + opts.tld] | ||
* ] - The hosts to use for Algolia Search API. It this your responsibility to shuffle the hosts and add a DSN host in it | ||
* @param {string} [opts.tld='net'] - The tld to use when computing hosts default list | ||
*/ | ||
var AlgoliaSearch = function(applicationID, apiKey, methodOrOptions, resolveDNS, hosts) { | ||
var self = this; | ||
this.applicationID = applicationID; | ||
this.apiKey = apiKey; | ||
this.dsn = true; | ||
this.dsnHost = null; | ||
this.hosts = []; | ||
this.currentHostIndex = 0; | ||
this.requestTimeoutInMs = 2000; | ||
this.extraHeaders = []; | ||
this.jsonp = null; | ||
this.options = {}; | ||
function AlgoliaSearch(applicationID, apiKey, opts, _request) { | ||
var usage = 'Usage: algoliasearch(applicationID, apiKey, opts)'; | ||
// make sure every client instance has it's own cache | ||
this.cache = {}; | ||
if (!applicationID) { | ||
throw new Error('Please provide an application ID. ' + usage); | ||
} | ||
var method; | ||
var tld = 'net'; | ||
if (typeof methodOrOptions === 'string') { // Old initialization | ||
method = methodOrOptions; | ||
} else { | ||
// Take all option from the hash | ||
var options = methodOrOptions || {}; | ||
this.options = options; | ||
if (!this._isUndefined(options.method)) { | ||
method = options.method; | ||
} | ||
if (!this._isUndefined(options.tld)) { | ||
tld = options.tld; | ||
} | ||
if (!this._isUndefined(options.dsn)) { | ||
this.dsn = options.dsn; | ||
} | ||
if (!this._isUndefined(options.hosts)) { | ||
hosts = options.hosts; | ||
} | ||
if (!this._isUndefined(options.dsnHost)) { | ||
this.dsnHost = options.dsnHost; | ||
} | ||
if (!this._isUndefined(options.requestTimeoutInMs)) { | ||
this.requestTimeoutInMs = +options.requestTimeoutInMs; | ||
} | ||
if (!this._isUndefined(options.jsonp)) { | ||
this.jsonp = options.jsonp; | ||
} | ||
if (!apiKey) { | ||
throw new Error('Please provide an API key. ' + usage); | ||
} | ||
// If hosts is undefined, initialize it with applicationID | ||
if (this._isUndefined(hosts)) { | ||
hosts = [ | ||
this.applicationID + '-1.algolia.' + tld, | ||
this.applicationID + '-2.algolia.' + tld, | ||
this.applicationID + '-3.algolia.' + tld | ||
]; | ||
opts = opts || {}; | ||
// now setting default options | ||
// could not find a tiny module to do that, let's go manual | ||
if (opts.timeout === undefined) { | ||
opts.timeout = 2000; | ||
} | ||
// detect is we use http or https | ||
this.host_protocol = 'http://'; | ||
if (this._isUndefined(method) || method === null) { | ||
this.host_protocol = ('https:' == document.location.protocol ? 'https' : 'http') + '://'; | ||
} else if (method === 'https' || method === 'HTTPS') { | ||
this.host_protocol = 'https://'; | ||
if (opts.protocol === undefined) { | ||
opts.protocol = document && document.location.protocol || 'http:'; | ||
} | ||
// Add hosts in random order | ||
for (var i = 0; i < hosts.length; ++i) { | ||
if (Math.random() > 0.5) { | ||
this.hosts.reverse(); | ||
} | ||
this.hosts.push(this.host_protocol + hosts[i]); | ||
if (opts.hosts === undefined) { | ||
opts.hosts = []; // filled later on, has dependencies | ||
} | ||
if (Math.random() > 0.5) { | ||
this.hosts.reverse(); | ||
if (opts.tld === undefined) { | ||
opts.tld = 'net'; | ||
} | ||
// then add Distributed Search Network host if there is one | ||
if (this.dsn || this.dsnHost != null) { | ||
if (this.dsnHost) { | ||
this.hosts.unshift(this.host_protocol + this.dsnHost); | ||
} else { | ||
this.hosts.unshift(this.host_protocol + this.applicationID + '-dsn.algolia.' + tld); | ||
} | ||
// while we advocate for colon-at-the-end values: 'http:' for `opts.protocol` | ||
// we also accept `http` and `https`. It's a common error. | ||
if (!/:$/.test(opts.protocol)) { | ||
opts.protocol = opts.protocol + ':'; | ||
} | ||
// angular dependencies injection | ||
if (this.options.angular) { | ||
this.options.angular.$injector.invoke(['$http', '$q', function ($http, $q) { | ||
self.options.angular.$q = $q; | ||
self.options.angular.$http = $http; | ||
}]); | ||
} | ||
}; | ||
// This holds the number of JSONP requests done accross clients | ||
// It's used as part of the ?callback=JSONP_$JSONPCounter when we do JSONP requests | ||
AlgoliaSearch.JSONPCounter = 0; | ||
// no hosts given, add defaults | ||
if (opts.hosts.length === 0) { | ||
opts.hosts = shuffle([ | ||
applicationID + '-1.algolia.' + opts.tld, | ||
applicationID + '-2.algolia.' + opts.tld, | ||
applicationID + '-3.algolia.' + opts.tld | ||
]); | ||
function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) { | ||
function _getHitExplanationForOneAttr_recurse(obj, foundWords) { | ||
var res = []; | ||
if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) { | ||
var match = false; | ||
for (var j = 0; j < obj.matchedWords.length; ++j) { | ||
var word = obj.matchedWords[j]; | ||
if (!(word in foundWords)) { | ||
foundWords[word] = 1; | ||
match = true; | ||
} | ||
} | ||
if (match) { | ||
res.push(obj.value); | ||
} | ||
} else if (Object.prototype.toString.call(obj) === '[object Array]') { | ||
for (var i = 0; i < obj.length; ++i) { | ||
var array = _getHitExplanationForOneAttr_recurse(obj[i], foundWords); | ||
res = res.concat(array); | ||
} | ||
} else if (typeof obj === 'object') { | ||
for (var prop in obj) { | ||
if (obj.hasOwnProperty(prop)){ | ||
res = res.concat(_getHitExplanationForOneAttr_recurse(obj[prop], foundWords)); | ||
} | ||
} | ||
} | ||
return res; | ||
// add default dsn host | ||
opts.hosts.unshift(applicationID + '-dsn.algolia.' + opts.tld); | ||
} | ||
function _getHitExplanationForOneAttr(hit, foundWords, attr) { | ||
var base = hit._highlightResult || hit; | ||
if (attr.indexOf('.') === -1) { | ||
if (attr in base) { | ||
return _getHitExplanationForOneAttr_recurse(base[attr], foundWords); | ||
} | ||
return []; | ||
} | ||
var array = attr.split('.'); | ||
var obj = base; | ||
for (var i = 0; i < array.length; ++i) { | ||
if (Object.prototype.toString.call(obj) === '[object Array]') { | ||
var res = []; | ||
for (var j = 0; j < obj.length; ++j) { | ||
res = res.concat(_getHitExplanationForOneAttr(obj[j], foundWords, array.slice(i).join('.'))); | ||
} | ||
return res; | ||
} | ||
if (array[i] in obj) { | ||
obj = obj[array[i]]; | ||
} else { | ||
return []; | ||
} | ||
} | ||
return _getHitExplanationForOneAttr_recurse(obj, foundWords); | ||
} | ||
opts.hosts = map(opts.hosts, function prependProtocol(host) { | ||
return opts.protocol + '//' + host; | ||
}); | ||
var res = {}; | ||
var foundWords = {}; | ||
var title = _getHitExplanationForOneAttr(hit, foundWords, titleAttribute); | ||
res.title = (title.length > 0) ? title[0] : ''; | ||
res.subtitles = []; | ||
this.applicationID = applicationID; | ||
this.apiKey = apiKey; | ||
this.hosts = opts.hosts; | ||
if (typeof otherAttributes !== 'undefined') { | ||
for (var i = 0; i < otherAttributes.length; ++i) { | ||
var attr = _getHitExplanationForOneAttr(hit, foundWords, otherAttributes[i]); | ||
for (var j = 0; j < attr.length; ++j) { | ||
res.subtitles.push({ attr: otherAttributes[i], value: attr[j] }); | ||
} | ||
} | ||
} | ||
return res; | ||
this.currentHostIndex = 0; | ||
this.requestTimeout = opts.timeout; | ||
this.extraHeaders = []; | ||
this.cache = {}; | ||
this._request = _request; | ||
} | ||
AlgoliaSearch.prototype = { | ||
@@ -209,3 +90,3 @@ /* | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains the task ID | ||
@@ -223,3 +104,3 @@ */ | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains the task ID | ||
@@ -240,3 +121,3 @@ */ | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains the task ID | ||
@@ -256,11 +137,15 @@ */ | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains the task ID | ||
*/ | ||
getLogs: function(callback, offset, length) { | ||
if (this._isUndefined(offset)) { | ||
getLogs: function(offset, length, callback) { | ||
if (arguments.length === 0 || typeof offset === 'function') { | ||
// getLogs([cb]) | ||
callback = offset; | ||
offset = 0; | ||
} | ||
if (this._isUndefined(length)) { | ||
length = 10; | ||
} else if (arguments.length === 1 || typeof length === 'function') { | ||
// getLogs(1, [cb)] | ||
callback = length; | ||
length = 10; | ||
} | ||
@@ -275,9 +160,16 @@ | ||
* | ||
* @param page The page to retrieve, starting at 0. | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with index list or error description if success is false. | ||
* @param page The page to retrieve, starting at 0. | ||
* error: null or Error('message') | ||
* content: the server answer with index list | ||
*/ | ||
listIndexes: function(callback, page) { | ||
var params = typeof page !== 'undefined' ? '?page=' + page : ''; | ||
listIndexes: function(page, callback) { | ||
var params = ''; | ||
if (page === undefined || typeof page === 'function') { | ||
callback = page; | ||
} else { | ||
params = '?page=' + page; | ||
} | ||
return this._jsonRequest({ method: 'GET', | ||
@@ -301,4 +193,4 @@ url: '/1/indexes' + params, | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
@@ -313,5 +205,6 @@ listUserKeys: function(callback) { | ||
* | ||
* @param key | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
@@ -325,6 +218,6 @@ getUserKeyACL: function(key, callback) { | ||
* Delete an existing user key | ||
* | ||
* @param key | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
@@ -348,7 +241,11 @@ deleteUserKey: function(key, callback) { | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
addUserKey: function(acls, callback) { | ||
return this.addUserKeyWithValidity(acls, 0, 0, 0, callback); | ||
return this.addUserKeyWithValidity(acls, { | ||
validity: 0, | ||
maxQueriesPerIPPerHour: 0, | ||
maxHitsPerQuery: 0 | ||
}, callback); | ||
}, | ||
@@ -366,15 +263,15 @@ /* | ||
* - editSettings : allows to change index settings (https only) | ||
* @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) | ||
* @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. | ||
* @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. | ||
* @param params.validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) | ||
* @param params.maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. | ||
* @param params.maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { | ||
addUserKeyWithValidity: function(acls, params, callback) { | ||
var aclsObject = {}; | ||
aclsObject.acl = acls; | ||
aclsObject.validity = validity; | ||
aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; | ||
aclsObject.maxHitsPerQuery = maxHitsPerQuery; | ||
aclsObject.validity = params.validity; | ||
aclsObject.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour; | ||
aclsObject.maxHitsPerQuery = params.maxHitsPerQuery; | ||
return this._jsonRequest({ method: 'POST', | ||
@@ -459,19 +356,12 @@ url: '/1/keys', | ||
* @param callback the function that will receive results | ||
* @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime. | ||
*/ | ||
sendQueriesBatch: function(callback, delay) { | ||
sendQueriesBatch: function(callback) { | ||
var as = this; | ||
var params = {requests: []}; | ||
for (var i = 0; i < as.batch.length; ++i) { | ||
params.requests.push(as.batch[i]); | ||
} | ||
window.clearTimeout(as.onDelayTrigger); | ||
if (!this._isUndefined(delay) && delay !== null && delay > 0) { | ||
var onDelayTrigger = window.setTimeout( function() { | ||
as._sendQueriesBatch(params, callback); | ||
}, delay); | ||
as.onDelayTrigger = onDelayTrigger; | ||
} else { | ||
return this._sendQueriesBatch(params, callback); | ||
} | ||
return this._sendQueriesBatch(params, callback); | ||
}, | ||
@@ -484,6 +374,5 @@ | ||
*/ | ||
setRequestTimeout: function(milliseconds) | ||
{ | ||
setRequestTimeout: function(milliseconds) { | ||
if (milliseconds) { | ||
this.requestTimeoutInMs = parseInt(milliseconds, 10); | ||
this.requestTimeout = parseInt(milliseconds, 10); | ||
} | ||
@@ -516,38 +405,20 @@ }, | ||
_sendQueriesBatch: function(params, callback) { | ||
if (this.jsonp === null) { | ||
var self = this; | ||
return this._jsonRequest({ cache: this.cache, | ||
method: 'POST', | ||
url: '/1/indexes/*/queries', | ||
body: params, | ||
callback: function(success, content) { | ||
if (!success) { | ||
// retry first with JSONP | ||
self.jsonp = true; | ||
self._sendQueriesBatch(params, callback); | ||
} else { | ||
self.jsonp = false; | ||
callback && callback(success, content); | ||
return this._jsonRequest({ cache: this.cache, | ||
method: 'POST', | ||
url: '/1/indexes/*/queries', | ||
body: params, | ||
fallback: { | ||
method: 'GET', | ||
url: '/1/indexes/*', | ||
body: {params: (function() { | ||
var reqParams = ''; | ||
for (var i = 0; i < params.requests.length; ++i) { | ||
var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params; | ||
reqParams += i + '=' + encodeURIComponent(q) + '&'; | ||
} | ||
} | ||
}); | ||
} else if (this.jsonp) { | ||
var jsonpParams = ''; | ||
for (var i = 0; i < params.requests.length; ++i) { | ||
var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params; | ||
jsonpParams += i + '=' + encodeURIComponent(q) + '&'; | ||
} | ||
var pObj = {params: jsonpParams}; | ||
return this._jsonRequest({ cache: this.cache, | ||
method: 'GET', | ||
url: '/1/indexes/*', | ||
body: pObj, | ||
callback: callback }); | ||
} else { | ||
return this._jsonRequest({ cache: this.cache, | ||
method: 'POST', | ||
url: '/1/indexes/*/queries', | ||
body: params, | ||
callback: callback}); | ||
} | ||
return reqParams; | ||
}())} | ||
}, | ||
callback: callback | ||
}); | ||
}, | ||
@@ -558,419 +429,163 @@ /* | ||
_jsonRequest: function(opts) { | ||
var self = this; | ||
var callback = opts.callback; | ||
var cache = null; | ||
// handle opts.fallback, automatically use fallback (JSONP in browser plugins, wrapped with $plugin-promises) | ||
// so if an error occurs and max tries => use fallback | ||
// set tries to 0 again | ||
// if fallback used and no more tries, return error | ||
// fallback parameters are in opts.fallback | ||
// call request.fallback or request accordingly, same promise chain otherwise | ||
// put callback& params in front if problem | ||
var cache = opts.cache; | ||
var cacheID = opts.url; | ||
var deferred = null; | ||
if (this.options.jQuery) { | ||
deferred = this.options.jQuery.$.Deferred(); | ||
deferred.promise = deferred.promise(); // promise is a property in angular | ||
} else if (this.options.angular) { | ||
deferred = this.options.angular.$q.defer(); | ||
} | ||
var client = this; | ||
var tries = 0; | ||
if (!this._isUndefined(opts.body)) { | ||
cacheID = opts.url + '_body_' + JSON.stringify(opts.body); | ||
// as we use POST requests to pass parameters (like query='aa'), | ||
// the cacheID must be different between calls | ||
if (opts.body !== undefined) { | ||
cacheID += '_body_' + JSON.stringify(opts.body); | ||
} | ||
if (!this._isUndefined(opts.cache)) { | ||
cache = opts.cache; | ||
if (!this._isUndefined(cache[cacheID])) { | ||
if (!this._isUndefined(callback) && callback) { | ||
setTimeout(function () { callback(true, cache[cacheID]); }, 1); | ||
} | ||
deferred && deferred.resolve(cache[cacheID]); | ||
return deferred && deferred.promise; | ||
} | ||
} | ||
opts.successiveRetryCount = 0; | ||
var impl = function() { | ||
if (opts.successiveRetryCount >= self.hosts.length) { | ||
var error = { message: 'Cannot connect the Algolia\'s Search API. Please send an email to support@algolia.com to report the issue.' }; | ||
if (!self._isUndefined(callback) && callback) { | ||
opts.successiveRetryCount = 0; | ||
callback(false, error); | ||
} | ||
deferred && deferred.reject(error); | ||
return; | ||
function doRequest(requester, reqOpts) { | ||
// handle cache existence | ||
if (cache && cache[cacheID] !== undefined) { | ||
return client._request.resolve(cache[cacheID]); | ||
} | ||
opts.callback = function(retry, success, body) { | ||
if (success && !self._isUndefined(opts.cache)) { | ||
cache[cacheID] = body; | ||
} | ||
if (!success && retry) { | ||
self.currentHostIndex = ++self.currentHostIndex % self.hosts.length; | ||
opts.successiveRetryCount += 1; | ||
impl(); | ||
} else { | ||
opts.successiveRetryCount = 0; | ||
deferred && (success ? deferred.resolve(body) : deferred.reject(body)); | ||
if (!self._isUndefined(callback) && callback) { | ||
callback(success, body); | ||
} | ||
} | ||
}; | ||
opts.hostname = self.hosts[self.currentHostIndex]; | ||
self._jsonRequestByHost(opts); | ||
}; | ||
impl(); | ||
return deferred && deferred.promise; | ||
}, | ||
_jsonRequestByHost: function(opts) { | ||
var self = this; | ||
var url = opts.hostname + opts.url; | ||
if (this.jsonp) { | ||
this._makeJsonpRequestByHost(url, opts); | ||
} else if (this.options.jQuery) { | ||
this._makejQueryRequestByHost(url, opts); | ||
} else if (this.options.angular) { | ||
this._makeAngularRequestByHost(url, opts); | ||
} else { | ||
this._makeXmlHttpRequestByHost(url, opts); | ||
} | ||
}, | ||
/** | ||
* Make a $http | ||
* | ||
* @param url request url (includes endpoint and path) | ||
* @param opts all request opts | ||
*/ | ||
_makeAngularRequestByHost: function(url, opts) { | ||
var self = this; | ||
var body = null; | ||
if (!this._isUndefined(opts.body)) { | ||
body = JSON.stringify(opts.body); | ||
} | ||
url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey; | ||
url += '&X-Algolia-Application-Id=' + this.applicationID; | ||
if (this.userToken) { | ||
url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken); | ||
} | ||
if (this.tagFilters) { | ||
url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters); | ||
} | ||
for (var i = 0; i < this.extraHeaders.length; ++i) { | ||
url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value; | ||
} | ||
this.options.angular.$http({ | ||
url: url, | ||
method: opts.method, | ||
data: body, | ||
cache: false, | ||
timeout: (this.requestTimeoutInMs * (opts.successiveRetryCount + 1)) | ||
}).then(function(response) { | ||
opts.callback(false, true, response.data); | ||
}, function(response) { | ||
if (response.status === 0) { | ||
// xhr.timeout is not handled by Angular.js right now | ||
// let's retry | ||
opts.callback(true, false, response.data); | ||
} else if (response.status == 400 || response.status === 403 || response.status === 404) { | ||
opts.callback(false, false, response.data); | ||
} else { | ||
opts.callback(true, false, response.data); | ||
} | ||
}); | ||
}, | ||
/** | ||
* Make a $.ajax | ||
* | ||
* @param url request url (includes endpoint and path) | ||
* @param opts all request opts | ||
*/ | ||
_makejQueryRequestByHost: function(url, opts) { | ||
var self = this; | ||
var body = null; | ||
if (!this._isUndefined(opts.body)) { | ||
body = JSON.stringify(opts.body); | ||
} | ||
url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey; | ||
url += '&X-Algolia-Application-Id=' + this.applicationID; | ||
if (this.userToken) { | ||
url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken); | ||
} | ||
if (this.tagFilters) { | ||
url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters); | ||
} | ||
for (var i = 0; i < this.extraHeaders.length; ++i) { | ||
url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value; | ||
} | ||
this.options.jQuery.$.ajax(url, { | ||
type: opts.method, | ||
timeout: (this.requestTimeoutInMs * (opts.successiveRetryCount + 1)), | ||
dataType: 'json', | ||
data: body, | ||
error: function(xhr, textStatus, error) { | ||
if (textStatus === 'timeout') { | ||
opts.callback(true, false, { 'message': 'Timeout - Could not connect to endpoint ' + url } ); | ||
} else if (xhr.status === 400 || xhr.status === 403 || xhr.status === 404) { | ||
opts.callback(false, false, xhr.responseJSON ); | ||
} else { | ||
opts.callback(true, false, { 'message': error } ); | ||
if (tries >= client.hosts.length) { | ||
if (!opts.fallback || requester === client._request.fallback) { | ||
// could not get a response even using the fallback if one was available | ||
return client._request.reject(new Error( | ||
'Cannot connect to the AlgoliaSearch API.' + | ||
' Send an email to support@algolia.com to report and resolve the issue.' | ||
)); | ||
} | ||
}, | ||
success: function(data, textStatus, xhr) { | ||
opts.callback(false, true, data); | ||
} | ||
}); | ||
}, | ||
/** | ||
* Make a JSONP request | ||
* | ||
* @param url request url (includes endpoint and path) | ||
* @param opts all request options | ||
*/ | ||
_makeJsonpRequestByHost: function(url, opts) { | ||
if (opts.method !== 'GET') { | ||
opts.callback(true, false, { 'message': 'Method ' + opts.method + ' ' + url + ' is not supported by JSONP.' }); | ||
return; | ||
} | ||
var cbCalled = false; | ||
var timedOut = false; | ||
AlgoliaSearch.JSONPCounter += 1; | ||
var head = document.getElementsByTagName('head')[0]; | ||
var script = document.createElement('script'); | ||
var cb = 'algoliaJSONP_' + AlgoliaSearch.JSONPCounter; | ||
var done = false; | ||
var ontimeout; | ||
var success; | ||
var clean; | ||
window[cb] = function(data) { | ||
try { delete window[cb]; } catch (e) { window[cb] = undefined; } | ||
if (timedOut) { | ||
return; | ||
tries = 0; | ||
reqOpts.method = opts.fallback.method; | ||
reqOpts.url = opts.fallback.url; | ||
reqOpts.body = opts.fallback.body; | ||
reqOpts.timeout = client.requestTimeout * (tries + 1); | ||
client.currentHostIndex = 0; | ||
client.forceFallback = true; | ||
return doRequest(client._request.fallback, reqOpts); | ||
} | ||
var status = | ||
data && data.message && data.status || | ||
data && 200; | ||
var url = reqOpts.url; | ||
var ok = status === 200; | ||
var retry = !ok && status !== 400 && status !== 403 && status !== 404; | ||
cbCalled = true; | ||
opts.callback(retry, ok, data); | ||
}; | ||
url += (url.indexOf('?') === -1 ? '?' : '&') + 'X-Algolia-API-Key=' + client.apiKey; | ||
url += '&X-Algolia-Application-Id=' + client.applicationID; | ||
script.type = 'text/javascript'; | ||
url += '?callback=' + cb + '&X-Algolia-Application-Id=' + this.applicationID + '&X-Algolia-API-Key=' + this.apiKey; | ||
if (this.tagFilters) { | ||
url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters); | ||
} | ||
if (this.userToken) { | ||
url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken); | ||
} | ||
for (var i = 0; i < this.extraHeaders.length; ++i) { | ||
url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value; | ||
} | ||
if (opts.body && opts.body.params) { | ||
url += '&' + opts.body.params; | ||
} | ||
ontimeout = setTimeout(function() { | ||
timedOut = true; | ||
clean(); | ||
opts.callback(true, false, { 'message': 'Timeout - Failed to load JSONP script.' }); | ||
}, this.requestTimeoutInMs); | ||
success = function() { | ||
if (done || timedOut) { | ||
return; | ||
if (client.userToken) { | ||
url += '&X-Algolia-UserToken=' + encodeURIComponent(client.userToken); | ||
} | ||
done = true; | ||
clean(); | ||
// script loaded but did not call the fn => script loading error | ||
if (!cbCalled) { | ||
opts.callback(true, false, { 'message': 'Failed to load JSONP script.' }); | ||
if (client.tagFilters) { | ||
url += '&X-Algolia-TagFilters=' + encodeURIComponent(client.tagFilters); | ||
} | ||
}; | ||
clean = function() { | ||
clearTimeout(ontimeout); | ||
script.onload = null; | ||
script.onreadystatechange = null; | ||
script.onerror = null; | ||
head.removeChild(script); | ||
try { | ||
delete window[cb]; | ||
delete window[cb + '_loaded']; | ||
} catch (e) { | ||
window[cb] = null; | ||
window[cb + '_loaded'] = null; | ||
for (var i = 0; i < client.extraHeaders.length; ++i) { | ||
url += '&' + client.extraHeaders[i].key + '=' + client.extraHeaders[i].value; | ||
} | ||
}; | ||
// script onreadystatechange needed only for | ||
// <= IE8 | ||
// https://github.com/angular/angular.js/issues/4523 | ||
script.onreadystatechange = function() { | ||
if (this.readyState === 'loaded' || this.readyState === 'complete') { | ||
success(); | ||
} | ||
}; | ||
return requester(client.hosts[client.currentHostIndex] + url, { | ||
body: reqOpts.body, | ||
method: reqOpts.method, | ||
timeout: reqOpts.timeout | ||
}) | ||
.then(function success(httpResponse) { | ||
// timeout case, retry immediately | ||
if (httpResponse instanceof Error) { | ||
return retryRequest(); | ||
} | ||
script.onload = function() { | ||
success(); | ||
}; | ||
var status = | ||
// When in browser mode, using XDR or JSONP | ||
// We rely on our own API response `status`, only | ||
// provided when an error occurs, we also expect a .message along | ||
// Otherwise, it could be a `waitTask` status, that's the only | ||
// case where we have a response.status that's not the http statusCode | ||
httpResponse && httpResponse.body && httpResponse.body.message && httpResponse.body.status || | ||
script.onerror = function() { | ||
if (done || timedOut) { | ||
return; | ||
} | ||
// this is important to check the request statusCode AFTER the body eventual | ||
// statusCode because some implementations (jQuery XDomainRequest transport) may | ||
// send statusCode 200 while we had an error | ||
httpResponse.statusCode || | ||
clean(); | ||
opts.callback(true, false, { 'message': 'Failed to load JSONP script.' }); | ||
}; | ||
// When in browser mode, using XDR or JSONP | ||
// we default to success when no error (no response.status && response.message) | ||
// If there was a JSON.parse() error then body is null and it fails | ||
httpResponse && httpResponse.body && 200; | ||
script.async = true; | ||
script.defer = true; | ||
script.src = url; | ||
var ok = status === 200 || status === 201; | ||
var retry = !ok && Math.floor(status / 100) !== 4 && Math.floor(status / 100) !== 1; | ||
head.appendChild(script); | ||
}, | ||
if (ok && cache) { | ||
cache[cacheID] = httpResponse.body; | ||
} | ||
/** | ||
* Make a XmlHttpRequest | ||
* | ||
* @param url request url (includes endpoint and path) | ||
* @param opts all request opts | ||
*/ | ||
_makeXmlHttpRequestByHost: function(url, opts) { | ||
// no cors or XDomainRequest, no request | ||
if (!this._support.cors && !this._support.hasXDomainRequest) { | ||
// very old browser, not supported | ||
opts.callback(false, false, { 'message': 'CORS not supported' }); | ||
return; | ||
} | ||
if (ok) { | ||
return httpResponse.body; | ||
} | ||
var body = null; | ||
var request = this._support.cors ? new XMLHttpRequest() : new XDomainRequest(); | ||
var ontimeout; | ||
var self = this; | ||
var timedOut; | ||
var timeoutListener; | ||
if (retry) { | ||
return retryRequest(); | ||
} | ||
if (!this._isUndefined(opts.body)) { | ||
body = JSON.stringify(opts.body); | ||
} | ||
var unrecoverableError = new Error( | ||
httpResponse.body && httpResponse.body.message || 'Unknown error' | ||
); | ||
url += (url.indexOf('?') === -1 ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey; | ||
url += '&X-Algolia-Application-Id=' + this.applicationID; | ||
return client._request.reject(unrecoverableError); | ||
}, tryFallback); | ||
if (this.userToken) { | ||
url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken); | ||
} | ||
function retryRequest() { | ||
client.currentHostIndex = ++client.currentHostIndex % client.hosts.length; | ||
tries += 1; | ||
reqOpts.timeout = client.requestTimeout * (tries + 1); | ||
return doRequest(requester, reqOpts); | ||
} | ||
if (this.tagFilters) { | ||
url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters); | ||
} | ||
function tryFallback() { | ||
// if we are switching to fallback right now, set tries to maximum | ||
if (!client.forceFallback) { | ||
// next time doRequest is called, simulate we tried all hosts | ||
tries = client.hosts.length; | ||
} else { | ||
// we were already using the fallback, but something went wrong (script error) | ||
client.currentHostIndex = ++client.currentHostIndex % client.hosts.length; | ||
tries += 1; | ||
} | ||
for (var i = 0; i < this.extraHeaders.length; ++i) { | ||
url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value; | ||
} | ||
timeoutListener = function() { | ||
if (!self._support.timeout) { | ||
timedOut = true; | ||
request.abort(); | ||
return doRequest(requester, reqOpts); | ||
} | ||
opts.callback(true, false, { 'message': 'Timeout - Could not connect to endpoint ' + url } ); | ||
}; | ||
// do not rely on default XHR async flag, as some analytics code like hotjar | ||
// breaks it and set it to false by default | ||
if (request instanceof XMLHttpRequest) { | ||
request.open(opts.method, url, true); | ||
} else { | ||
request.open(opts.method, url); | ||
} | ||
if (this._support.cors && body !== null && opts.method !== 'GET') { | ||
request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); | ||
} | ||
// we can use a fallback if forced AND fallback parameters are available | ||
var useFallback = client.forceFallback && opts.fallback; | ||
var requestOptions = useFallback ? opts.fallback : opts; | ||
// event object not received in IE8, at least | ||
// but we do not use it, still important to note | ||
request.onload = function(/*event*/) { | ||
// When browser does not supports request.timeout, we can | ||
// have both a load and timeout event | ||
if (timedOut) { | ||
return; | ||
var promise = doRequest( | ||
useFallback ? client._request.fallback : client._request, { | ||
url: requestOptions.url, | ||
method: requestOptions.method, | ||
body: requestOptions.body, | ||
timeout: client.requestTimeout * (tries + 1) | ||
} | ||
); | ||
if (!self._support.timeout) { | ||
clearTimeout(ontimeout); | ||
} | ||
var response = null; | ||
try { | ||
response = JSON.parse(request.responseText); | ||
} catch(e) {} | ||
var status = | ||
// XHR provides a `status` property | ||
request.status || | ||
// XDR does not have a `status` property, | ||
// we rely on our own API response `status`, only | ||
// provided when an error occurs, so we expect a .message | ||
response && response.message && response.status || | ||
// XDR default to success when no response.status | ||
response && 200; | ||
var success = status === 200 || status === 201; | ||
var retry = !success && status !== 400 && status !== 403 && status !== 404; | ||
opts.callback(retry, success, response); | ||
}; | ||
if (this._support.timeout) { | ||
// .timeout supported by both XHR and XDR, | ||
// we do receive timeout event, tested | ||
request.timeout = this.requestTimeoutInMs * (opts.successiveRetryCount + 1); | ||
request.ontimeout = timeoutListener; | ||
// either we have a callback | ||
// either we are using promises | ||
if (opts.callback) { | ||
promise.then(function okCb(content) { | ||
process.nextTick(function() { | ||
opts.callback(null, content); | ||
}); | ||
}, function nookCb(err) { | ||
process.nextTick(function() { | ||
opts.callback(err); | ||
}); | ||
}); | ||
} else { | ||
ontimeout = setTimeout(timeoutListener, this.requestTimeoutInMs * (opts.successiveRetryCount + 1)); | ||
return promise; | ||
} | ||
request.onerror = function(event) { | ||
if (timedOut) { | ||
return; | ||
} | ||
if (!self._support.timeout) { | ||
clearTimeout(ontimeout); | ||
} | ||
// error event is trigerred both with XDR/XHR on: | ||
// - DNS error | ||
// - unallowed cross domain request | ||
opts.callback(true, false, { 'message': 'Could not connect to host', 'error': event } ); | ||
}; | ||
request.send(body); | ||
}, | ||
@@ -987,3 +602,3 @@ | ||
if (key !== null && args.hasOwnProperty(key)) { | ||
params += (params.length === 0) ? '?' : '&'; | ||
params += params === '' ? '' : '&'; | ||
params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]); | ||
@@ -996,9 +611,2 @@ } | ||
return obj === void 0; | ||
}, | ||
_support: { | ||
hasXMLHttpRequest: 'XMLHttpRequest' in window, | ||
hasXDomainRequest: 'XDomainRequest' in window, | ||
cors: 'withCredentials' in new XMLHttpRequest(), | ||
timeout: 'timeout' in new XMLHttpRequest() | ||
} | ||
@@ -1022,22 +630,25 @@ }; | ||
* @param content contains the javascript object to add inside the index | ||
* @param objectID (optional) an objectID you want to attribute to this object | ||
* (if the attribute already exist the old object will be overwrite) | ||
* @param callback (optional) the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains 3 elements: createAt, taskId and objectID | ||
* @param objectID (optional) an objectID you want to attribute to this object | ||
* (if the attribute already exist the old object will be overwrite) | ||
*/ | ||
addObject: function(content, callback, objectID) { | ||
addObject: function(content, objectID, callback) { | ||
var indexObj = this; | ||
if (this.as._isUndefined(objectID)) { | ||
return this.as._jsonRequest({ method: 'POST', | ||
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName), | ||
body: content, | ||
callback: callback }); | ||
} else { | ||
return this.as._jsonRequest({ method: 'PUT', | ||
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), | ||
body: content, | ||
callback: callback }); | ||
if (arguments.length === 1 || typeof objectID === 'function') { | ||
callback = objectID; | ||
objectID = undefined; | ||
} | ||
return this.as._jsonRequest({ | ||
method: objectID !== undefined ? | ||
'PUT' : // update or create | ||
'POST', // create (API generates an objectID) | ||
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + // create | ||
(objectID !== undefined ? '/' + encodeURIComponent(objectID) : ''), // update or create | ||
body: content, | ||
callback: callback | ||
}); | ||
}, | ||
@@ -1049,3 +660,3 @@ /* | ||
* @param callback (optional) the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that updateAt and taskID | ||
@@ -1055,3 +666,3 @@ */ | ||
var indexObj = this; | ||
var postObj = {requests:[]}; | ||
var postObj = {requests: []}; | ||
for (var i = 0; i < objects.length; ++i) { | ||
@@ -1071,27 +682,31 @@ var request = { action: 'addObject', | ||
* @param objectID the unique identifier of the object to retrieve | ||
* @param attrs (optional) if set, contains the array of attribute names to retrieve | ||
* @param callback (optional) the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the object to retrieve or the error message if a failure occured | ||
* @param attributes (optional) if set, contains the array of attribute names to retrieve | ||
*/ | ||
getObject: function(objectID, callback, attributes) { | ||
if (Object.prototype.toString.call(callback) === '[object Array]' && !attributes) { | ||
attributes = callback; | ||
callback = null; | ||
getObject: function(objectID, attrs, callback) { | ||
var indexObj = this; | ||
if (arguments.length === 1 || typeof attrs === 'function') { | ||
callback = attrs; | ||
attrs = undefined; | ||
} | ||
var indexObj = this; | ||
var params = ''; | ||
if (!this.as._isUndefined(attributes)) { | ||
if (attrs !== undefined) { | ||
params = '?attributes='; | ||
for (var i = 0; i < attributes.length; ++i) { | ||
for (var i = 0; i < attrs.length; ++i) { | ||
if (i !== 0) { | ||
params += ','; | ||
} | ||
params += attributes[i]; | ||
params += attrs[i]; | ||
} | ||
} | ||
return this.as._jsonRequest({ method: 'GET', | ||
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params, | ||
callback: callback }); | ||
return this.as._jsonRequest({ | ||
method: 'GET', | ||
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params, | ||
callback: callback | ||
}); | ||
}, | ||
@@ -1105,3 +720,3 @@ | ||
* @param callback (optional) the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains 3 elements: createAt, taskId and objectID | ||
@@ -1114,3 +729,3 @@ */ | ||
body: partialObject, | ||
callback: callback }); | ||
callback: callback }); | ||
}, | ||
@@ -1122,3 +737,3 @@ /* | ||
* @param callback (optional) the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that updateAt and taskID | ||
@@ -1128,3 +743,3 @@ */ | ||
var indexObj = this; | ||
var postObj = {requests:[]}; | ||
var postObj = {requests: []}; | ||
for (var i = 0; i < objects.length; ++i) { | ||
@@ -1146,3 +761,3 @@ var request = { action: 'partialUpdateObject', | ||
* @param callback (optional) the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that updateAt and taskID | ||
@@ -1162,3 +777,3 @@ */ | ||
* @param callback (optional) the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that updateAt and taskID | ||
@@ -1168,3 +783,3 @@ */ | ||
var indexObj = this; | ||
var postObj = {requests:[]}; | ||
var postObj = {requests: []}; | ||
for (var i = 0; i < objects.length; ++i) { | ||
@@ -1186,10 +801,16 @@ var request = { action: 'updateObject', | ||
* @param callback (optional) the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains 3 elements: createAt, taskId and objectID | ||
*/ | ||
deleteObject: function(objectID, callback) { | ||
if (objectID === null || objectID.length === 0) { | ||
callback(false, { message: 'empty objectID'}); | ||
return; | ||
if (typeof objectID === 'function' || typeof objectID !== 'string' && typeof objectID !== 'number') { | ||
var err = new Error('Cannot delete an object without an objectID'); | ||
callback = objectID; | ||
if (typeof callback === 'function') { | ||
return callback(err); | ||
} | ||
return this.as._request.reject(err); | ||
} | ||
var indexObj = this; | ||
@@ -1205,5 +826,2 @@ return this.as._jsonRequest({ method: 'DELETE', | ||
* @param query the full text query | ||
* @param callback the result callback with two arguments: | ||
* success: boolean set to true if the request was successfull. If false, the content contains the error. | ||
* content: the server answer that contains the list of results. | ||
* @param args (optional) if set, contains an object with query parameters: | ||
@@ -1268,34 +886,36 @@ * - page: (integer) Pagination parameter used to select the page to retrieve. | ||
* either comma separated or as an array | ||
* @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime. | ||
* @param callback the result callback with two arguments: | ||
* error: null or Error('message'). If false, the content contains the error. | ||
* content: the server answer that contains the list of results. | ||
*/ | ||
search: function(query, callback, args, delay) { | ||
if (query === undefined || query === null) { | ||
search: function(query, args, callback) { | ||
if (arguments.length === 0 || typeof query === 'function') { | ||
// .search(), .search(cb) | ||
callback = query; | ||
query = ''; | ||
} else if (arguments.length === 1 || typeof args === 'function') { | ||
// .search(query/args), .search(query, cb) | ||
callback = args; | ||
args = undefined; | ||
} | ||
// no query = getAllObjects | ||
if (typeof query === 'function') { | ||
callback = query; | ||
// .search(args), careful: typeof null === 'object' | ||
if (typeof query === 'object' && query !== null) { | ||
args = query; | ||
query = undefined; | ||
} else if (query === undefined || query === null) { // .search(undefined/null) | ||
query = ''; | ||
} | ||
if (typeof callback === 'object' && (this.as._isUndefined(args) || !args)) { | ||
args = callback; | ||
callback = null; | ||
var params = ''; | ||
if (query !== undefined) { | ||
params += 'query=' + encodeURIComponent(query); | ||
} | ||
var indexObj = this; | ||
var params = 'query=' + encodeURIComponent(query); | ||
if (!this.as._isUndefined(args) && args !== null) { | ||
if (args !== undefined) { | ||
params = this.as._getSearchParams(args, params); | ||
} | ||
window.clearTimeout(indexObj.onDelayTrigger); | ||
if (!this.as._isUndefined(delay) && delay !== null && delay > 0) { | ||
var onDelayTrigger = window.setTimeout( function() { | ||
indexObj._search(params, callback); | ||
}, delay); | ||
indexObj.onDelayTrigger = onDelayTrigger; | ||
} else { | ||
return this._search(params, callback); | ||
} | ||
return this._search(params, callback); | ||
}, | ||
@@ -1309,9 +929,14 @@ | ||
* @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000. | ||
* @param callback the result callback with two arguments: | ||
* error: null or Error('message'). If false, the content contains the error. | ||
* content: the server answer that contains the list of results. | ||
*/ | ||
browse: function(page, callback, hitsPerPage) { | ||
if (+callback > 0 && (this.as._isUndefined(hitsPerPage) || !hitsPerPage)) { | ||
hitsPerPage = callback; | ||
callback = null; | ||
browse: function(page, hitsPerPage, callback) { | ||
var indexObj = this; | ||
if (arguments.length === 1 || typeof hitsPerPage === 'function') { | ||
callback = hitsPerPage; | ||
hitsPerPage = undefined; | ||
} | ||
var indexObj = this; | ||
var params = '?page=' + page; | ||
@@ -1333,9 +958,10 @@ if (!this.as._isUndefined(hitsPerPage)) { | ||
return function(query, cb) { | ||
self.search(query, function(success, content) { | ||
if (success) { | ||
cb(content.hits); | ||
} else { | ||
cb(content && content.message); | ||
self.search(query, params, function(err, content) { | ||
if (err) { | ||
cb(err); | ||
return; | ||
} | ||
}, params); | ||
cb(content.hits); | ||
}); | ||
}; | ||
@@ -1350,20 +976,40 @@ }, | ||
* @param callback the result callback with with two arguments: | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer that contains the list of results | ||
*/ | ||
waitTask: function(taskID, callback) { | ||
// waitTask() must be handled differently from other methods, | ||
// it's a recursive method using a timeout | ||
var indexObj = this; | ||
return this.as._jsonRequest({ method: 'GET', | ||
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID, | ||
callback: function(success, body) { | ||
if (success) { | ||
if (body.status === 'published') { | ||
callback(true, body); | ||
} else { | ||
setTimeout(function() { indexObj.waitTask(taskID, callback); }, 100); | ||
} | ||
var promise = this.as._jsonRequest({ | ||
method: 'GET', | ||
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID | ||
}).then(function success(content) { | ||
if (content.status !== 'published') { | ||
return new indexObj.as._request.delay(100).then(function() { | ||
return indexObj.waitTask(taskID, callback); | ||
}); | ||
} | ||
if (callback) { | ||
process.nextTick(function() { | ||
callback(null, content); | ||
}); | ||
} else { | ||
callback(false, body); | ||
return content; | ||
} | ||
}}); | ||
}, function failure(err) { | ||
if (callback) { | ||
process.nextTick(function() { | ||
callback(err); | ||
}); | ||
} else { | ||
return err; | ||
} | ||
}); | ||
if (!callback) { | ||
return promise; | ||
} | ||
}, | ||
@@ -1375,3 +1021,3 @@ | ||
* @param callback (optional) the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the settings object or the error message if a failure occured | ||
@@ -1389,3 +1035,3 @@ */ | ||
* @param callback (optional) the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the settings object or the error message if a failure occured | ||
@@ -1451,3 +1097,3 @@ */ | ||
* @param callback (optional) the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* error: null or Error('message') | ||
* content: the server answer or the error message if a failure occured | ||
@@ -1466,4 +1112,4 @@ */ | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
@@ -1479,5 +1125,6 @@ listUserKeys: function(callback) { | ||
* | ||
* @param key | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
@@ -1493,5 +1140,6 @@ getUserKeyACL: function(key, callback) { | ||
* | ||
* @param key | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
@@ -1516,4 +1164,4 @@ deleteUserKey: function(key, callback) { | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
@@ -1540,16 +1188,16 @@ addUserKey: function(acls, callback) { | ||
* - editSettings : allows to change index settings (https only) | ||
* @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) | ||
* @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. | ||
* @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. | ||
* @param params.validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) | ||
* @param params.maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. | ||
* @param params.maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. | ||
* @param callback the result callback with two arguments | ||
* success: boolean set to true if the request was successfull | ||
* content: the server answer with user keys list or error description if success is false. | ||
* error: null or Error('message') | ||
* content: the server answer with user keys list | ||
*/ | ||
addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { | ||
addUserKeyWithValidity: function(acls, params, callback) { | ||
var indexObj = this; | ||
var aclsObject = {}; | ||
aclsObject.acl = acls; | ||
aclsObject.validity = validity; | ||
aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; | ||
aclsObject.maxHitsPerQuery = maxHitsPerQuery; | ||
aclsObject.validity = params.validity; | ||
aclsObject.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour; | ||
aclsObject.maxHitsPerQuery = params.maxHitsPerQuery; | ||
return this.as._jsonRequest({ method: 'POST', | ||
@@ -1564,33 +1212,13 @@ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', | ||
_search: function(params, callback) { | ||
var pObj = {params: params}; | ||
if (this.as.jsonp === null) { | ||
var self = this; | ||
return this.as._jsonRequest({ cache: this.cache, | ||
method: 'POST', | ||
url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query', | ||
body: pObj, | ||
callback: function(success, content) { | ||
var status = content && content.status; | ||
if (success || status && Math.floor(status / 100) === 4 || Math.floor(status / 100) === 1) { | ||
self.as.jsonp = false; | ||
callback && callback(success, content); | ||
} else { | ||
self.as.jsonp = true; | ||
self._search(params, callback); | ||
} | ||
} | ||
}); | ||
} else if (this.as.jsonp) { | ||
return this.as._jsonRequest({ cache: this.cache, | ||
method: 'GET', | ||
url: '/1/indexes/' + encodeURIComponent(this.indexName), | ||
body: pObj, | ||
callback: callback }); | ||
} else { | ||
return this.as._jsonRequest({ cache: this.cache, | ||
method: 'POST', | ||
url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query', | ||
body: pObj, | ||
callback: callback}); | ||
} | ||
return this.as._jsonRequest({ cache: this.cache, | ||
method: 'POST', | ||
url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query', | ||
body: {params: params}, | ||
fallback: { | ||
method: 'GET', | ||
url: '/1/indexes/' + encodeURIComponent(this.indexName), | ||
body: {params: params} | ||
}, | ||
callback: callback | ||
}); | ||
}, | ||
@@ -1604,1 +1232,36 @@ | ||
}; | ||
// extracted from https://github.com/component/map/blob/master/index.js | ||
// without the crazy toFunction thing | ||
function map(arr, fn){ | ||
var ret = []; | ||
for (var i = 0; i < arr.length; ++i) { | ||
ret.push(fn(arr[i], i)); | ||
} | ||
return ret; | ||
} | ||
// extracted from https://github.com/coolaj86/knuth-shuffle | ||
// not compatible with browserify | ||
function shuffle(array) { | ||
/*eslint-disable*/ | ||
var currentIndex = array.length | ||
, temporaryValue | ||
, randomIndex | ||
; | ||
// While there remain elements to shuffle... | ||
while (0 !== currentIndex) { | ||
// Pick a remaining element... | ||
randomIndex = Math.floor(Math.random() * currentIndex); | ||
currentIndex -= 1; | ||
// And swap it with the current element. | ||
temporaryValue = array[currentIndex]; | ||
array[currentIndex] = array[randomIndex]; | ||
array[randomIndex] = temporaryValue; | ||
} | ||
return array; | ||
} |
@@ -1,15 +0,1 @@ | ||
// make globals writable to IE<=8, | ||
// so that we can use sinon.useFakeTimers(); | ||
// this should be done by sinon.js but not in npm/commonJS world | ||
// see https://github.com/cjohansen/Sinon.JS/pull/600#issuecomment-76154721 | ||
require('writable-window-method')([ | ||
'setTimeout', | ||
'clearTimeout', | ||
'setImmediate', | ||
'clearImmediate', | ||
'setInterval', | ||
'clearInterval', | ||
'Date' | ||
]); | ||
var domready = require('domready'); | ||
@@ -16,0 +2,0 @@ |
@@ -22,3 +22,3 @@ var test = require('tape'); | ||
client.startQueriesBatch(); | ||
client.sendQueriesBatch(); | ||
client.sendQueriesBatch(makeSecondRequest); | ||
fauxJax.requests[0].respond(200, {}, '{}'); | ||
@@ -31,25 +31,29 @@ t.equal( | ||
// same request again | ||
client.startQueriesBatch(); | ||
client.sendQueriesBatch(); | ||
t.equal( | ||
fauxJax.requests.length, | ||
1, | ||
'Still one request done' | ||
); | ||
function makeSecondRequest() { | ||
// same request again | ||
client.startQueriesBatch(); | ||
client.sendQueriesBatch(makeThirdRequest); | ||
t.equal( | ||
fauxJax.requests.length, | ||
1, | ||
'Still one request done' | ||
); | ||
} | ||
client.clearCache(); | ||
function makeThirdRequest() { | ||
client.clearCache(); | ||
// same request again | ||
client.startQueriesBatch(); | ||
client.sendQueriesBatch(); | ||
// same request again | ||
client.startQueriesBatch(); | ||
client.sendQueriesBatch(); | ||
fauxJax.requests[1].respond(200, {}, '{}'); | ||
t.equal( | ||
fauxJax.requests.length, | ||
2, | ||
'Second request done' | ||
); | ||
fauxJax.requests[1].respond(200, {}, '{}'); | ||
t.equal( | ||
fauxJax.requests.length, | ||
2, | ||
'Second request done' | ||
); | ||
fauxJax.restore(); | ||
fauxJax.restore(); | ||
} | ||
}); |
@@ -6,3 +6,3 @@ var test = require('tape'); | ||
var AlgoliaSearch = require('algoliasearch'); | ||
var algoliasearch = require('../../../'); | ||
var bind = require('lodash-compat/function/bind'); | ||
@@ -14,5 +14,5 @@ | ||
var client = new AlgoliaSearch(credentials.applicationID, credentials.searchOnlyAPIKey); | ||
var client = algoliasearch(credentials.applicationID, credentials.searchOnlyAPIKey); | ||
t.doesNotThrow(bind(client.initIndex, client, credentials.indexName)); | ||
}); |
@@ -6,6 +6,6 @@ var test = require('tape'); | ||
// for it | ||
test('AlgoliaSearch.prototype API spec', function(t) { | ||
test('AlgoliaSearch client API spec', function(t) { | ||
t.plan(1); | ||
var AlgoliaSearch = require('algoliasearch'); | ||
var algoliasearch = require('../../../'); | ||
var filter = require('lodash-compat/collection/filter'); | ||
@@ -16,3 +16,3 @@ var functions = require('lodash-compat/object/functions'); | ||
var client = new AlgoliaSearch('test', 'methods'); | ||
var client = algoliasearch('test', 'methods'); | ||
@@ -19,0 +19,0 @@ var actualMethods = filter(functions(client), onlyPublicProperties).sort(); |
@@ -5,3 +5,3 @@ module.exports = [{ | ||
methodName: 'addUserKey', | ||
callArguments: [ ['search', 'browse'] ], | ||
callArguments: [['search', 'browse']], | ||
expectedRequest: { | ||
@@ -17,3 +17,9 @@ method: 'POST', | ||
methodName: 'addUserKeyWithValidity', | ||
callArguments: [ ['search', 'browse'], 42, 100, 10 ], | ||
callArguments: [ | ||
['search', 'browse'], { | ||
validity: 42, | ||
maxQueriesPerIPPerHour: 100, | ||
maxHitsPerQuery: 10 | ||
} | ||
], | ||
expectedRequest: { | ||
@@ -20,0 +26,0 @@ method: 'POST', |
@@ -33,3 +33,3 @@ var cloneDeep = require('lodash-compat/lang/cloneDeep'); | ||
testName: 'client.getLogs(cb, offset)', | ||
callArguments: [sinon.spy(), 25], | ||
callArguments: [25, sinon.spy()], | ||
expectedRequest: { | ||
@@ -48,3 +48,3 @@ URL: { | ||
testName: 'client.getLogs(cb, offset, length)', | ||
callArguments: [sinon.spy(), 30, 20], | ||
callArguments: [30, 20, sinon.spy()], | ||
expectedRequest: { | ||
@@ -51,0 +51,0 @@ URL: { |
@@ -17,3 +17,3 @@ var sinon = require('sinon'); | ||
methodName: 'listIndexes', | ||
callArguments: [sinon.spy(), 10], | ||
callArguments: [10, sinon.spy()], | ||
expectedRequest: { | ||
@@ -20,0 +20,0 @@ method: 'GET', |
@@ -21,3 +21,4 @@ var test = require('tape'); | ||
// store the query in the cache | ||
index.search('hey!'); | ||
index.search('hey!', makeSecondRequest); | ||
fauxJax.requests[0].respond(200, {}, '{}'); | ||
@@ -30,22 +31,28 @@ t.equal( | ||
// same request again | ||
index.search('hey!'); | ||
t.equal( | ||
fauxJax.requests.length, | ||
1, | ||
'Still one request done' | ||
); | ||
function makeSecondRequest() { | ||
// same request again | ||
index.search('hey!', makeThirdRequest); | ||
index.clearCache(); | ||
t.equal( | ||
fauxJax.requests.length, | ||
1, | ||
'Still one request done' | ||
); | ||
} | ||
// same request again | ||
index.search('hey!'); | ||
fauxJax.requests[1].respond(200, {}, '{}'); | ||
t.equal( | ||
fauxJax.requests.length, | ||
2, | ||
'Second request done' | ||
); | ||
function makeThirdRequest() { | ||
index.clearCache(); | ||
fauxJax.restore(); | ||
// same request again | ||
index.search('hey!'); | ||
fauxJax.requests[1].respond(200, {}, '{}'); | ||
t.equal( | ||
fauxJax.requests.length, | ||
2, | ||
'Second request done' | ||
); | ||
fauxJax.restore(); | ||
} | ||
}); |
@@ -6,6 +6,6 @@ var test = require('tape'); | ||
// for it | ||
test('AlgoliaSearch.prototype.Index.prototype API spec', function(t) { | ||
test('AlgoliaSearch index API spec', function(t) { | ||
t.plan(1); | ||
var AlgoliaSearch = require('algoliasearch'); | ||
var algoliasearch = require('../../../'); | ||
var filter = require('lodash-compat/collection/filter'); | ||
@@ -16,3 +16,3 @@ var functions = require('lodash-compat/object/functions'); | ||
var client = new AlgoliaSearch('test', 'methods'); | ||
var client = algoliasearch('test', 'methods'); | ||
var index = client.initIndex('himethods'); | ||
@@ -19,0 +19,0 @@ |
var fauxJax = require('faux-jax'); | ||
var sinon = require('sinon'); | ||
@@ -27,6 +26,6 @@ var testCases = module.exports = [{ | ||
methodName: 'addObject', | ||
testName: 'index.addObject(content, cb, objectID)', | ||
testName: 'index.addObject(content, objectID, cb)', | ||
callArguments: [{ | ||
yaw: 'two' | ||
}, sinon.spy(), 'dsa dsd/ sa'], | ||
}, 'dsa dsd/ sa'], | ||
expectedRequest: { | ||
@@ -33,0 +32,0 @@ method: 'PUT', |
@@ -5,3 +5,9 @@ module.exports = { | ||
testName: 'index.addUserKeyWithValidity(acls, cb)', | ||
callArguments: [['search', 'mom'], 42141, 421, 420], | ||
callArguments: [ | ||
['search', 'mom'], { | ||
validity: 42141, | ||
maxQueriesPerIPPerHour: 421, | ||
maxHitsPerQuery: 420 | ||
} | ||
], | ||
expectedRequest: { | ||
@@ -8,0 +14,0 @@ method: 'POST', |
@@ -1,3 +0,1 @@ | ||
var sinon = require('sinon'); | ||
module.exports = [{ | ||
@@ -20,4 +18,4 @@ object: 'index', | ||
methodName: 'browse', | ||
testName: 'index.browse(page, cb, hitsPerPage)', | ||
callArguments: [10, sinon.spy(), 15], | ||
testName: 'index.browse(page, hitsPerPage, cb)', | ||
callArguments: [10, 15], | ||
expectedRequest: { | ||
@@ -24,0 +22,0 @@ method: 'GET', |
@@ -1,3 +0,1 @@ | ||
var sinon = require('sinon'); | ||
module.exports = [{ | ||
@@ -15,6 +13,6 @@ testName: 'index.getObject(objectID, cb)', | ||
}, { | ||
testName: 'index.getObject(objectID, cb, attributes)', | ||
testName: 'index.getObject(objectID, attrs, cb)', | ||
object: 'index', | ||
methodName: 'getObject', | ||
callArguments: ['second object', sinon.spy(), ['some', 'attrs']], | ||
callArguments: ['second object', ['some', 'attrs']], | ||
expectedRequest: { | ||
@@ -21,0 +19,0 @@ method: 'GET', |
@@ -1,7 +0,5 @@ | ||
var sinon = require('sinon'); | ||
module.exports = [{ | ||
testName: 'index.search(query, cb, simpleParameters)', | ||
testName: 'index.search(query, simpleParameters, cb)', | ||
methodName: 'search', | ||
callArguments: ['some params', sinon.spy(), { | ||
callArguments: ['some params', { | ||
page: 1 | ||
@@ -17,7 +15,20 @@ }], | ||
}, { | ||
testName: 'index.search(query, cb, fullParameters)', | ||
testName: 'index.search(queryInParameter, cb)', | ||
methodName: 'search', | ||
callArguments: [{ | ||
query: 'dear Slim', | ||
page: 2 | ||
}], | ||
expectedRequest: { | ||
method: 'POST', | ||
URL: {pathname: '/1/indexes/%s/query'}, | ||
body: { | ||
params: 'query=dear%20Slim&page=2' | ||
} | ||
} | ||
}, { | ||
testName: 'index.search(query, fullParameters, cb)', | ||
methodName: 'search', | ||
callArguments: [ | ||
'full params', | ||
sinon.spy(), { | ||
'full params', { | ||
// https://www.algolia.com/doc/javascript#QueryParameters | ||
@@ -24,0 +35,0 @@ queryType: 'prefixAll', |
@@ -26,3 +26,3 @@ var testCases = module.exports = []; | ||
// there's an error (status 400), we will retry instead of stopping the wait loop | ||
if (support.xhr.hasXMLHttpRequest && support.xhr.cors) { | ||
if (support.xhr && support.xhr.cors) { | ||
testCases.push({ | ||
@@ -41,5 +41,8 @@ object: 'index', | ||
statusCode: 400, | ||
body: '' | ||
body: JSON.stringify({ | ||
message: 'woops!', | ||
status: 400 | ||
}) | ||
} | ||
}); | ||
} |
@@ -35,7 +35,7 @@ var test = require('tape'); | ||
); | ||
fauxJax.restore(); | ||
}); | ||
fauxJax.requests[0].respond(200, {}, JSON.stringify(fakeResponse)); | ||
fauxJax.restore(); | ||
}); |
var test = require('tape'); | ||
test('index.waitTask(taskID) retry', function(t) { | ||
t.plan(4); | ||
t.plan(9); | ||
@@ -9,4 +9,2 @@ var fauxJax = require('faux-jax'); | ||
var clock = sinon.useFakeTimers(); | ||
var createFixture = require('../../../utils/create-fixture'); | ||
@@ -16,12 +14,35 @@ var fixture = createFixture(); | ||
var index = fixture.index; | ||
var spy = sinon.spy(); | ||
var cbSpy = sinon.spy(function(err, content) { | ||
t.ok( | ||
cbSpy.calledOnce, | ||
'Callback called since task was published' | ||
); | ||
t.error(err, 'No error while using the callback waitTask API'); | ||
t.deepEqual(content, { | ||
status: 'published' | ||
}, 'Content matches'); | ||
}); | ||
var promiseSpy = sinon.spy(function(content) { | ||
t.deepEqual(content, { | ||
status: 'published' | ||
}, 'Content matches'); | ||
t.ok( | ||
promiseSpy.calledOnce, | ||
'Promise resolved once since task was published' | ||
); | ||
}); | ||
fauxJax.install(); | ||
index.waitTask(28000, spy); | ||
index.waitTask(28000, cbSpy); | ||
index.waitTask(27000).then(promiseSpy); | ||
t.equal( | ||
fauxJax.requests.length, | ||
1, | ||
'First request was made' | ||
2, | ||
'Two requests done' | ||
); | ||
@@ -37,17 +58,2 @@ | ||
clock.tick(50); | ||
t.notOk( | ||
spy.calledOnce, | ||
'Callback not called since task was not published' | ||
); | ||
clock.tick(60); | ||
t.equal( | ||
fauxJax.requests.length, | ||
2, | ||
'Second request was made' | ||
); | ||
fauxJax.requests[1].respond( | ||
@@ -57,13 +63,44 @@ 200, | ||
JSON.stringify({ | ||
status: 'published' | ||
status: 'notPublished' | ||
}) | ||
); | ||
fauxJax.restore(); | ||
clock.restore(); | ||
setTimeout(function() { | ||
process.nextTick(function() { | ||
t.notOk( | ||
cbSpy.calledOnce, | ||
'Callback not called since task was not published' | ||
); | ||
t.ok( | ||
spy.calledOnce, | ||
'Callback called since task was published' | ||
); | ||
t.notOk( | ||
promiseSpy.calledOnce, | ||
'Callback not called since task was not published' | ||
); | ||
t.equal( | ||
fauxJax.requests.length, | ||
4, | ||
'Four requests were made' | ||
); | ||
fauxJax.requests[2].respond( | ||
200, | ||
{}, | ||
JSON.stringify({ | ||
status: 'published' | ||
}) | ||
); | ||
fauxJax.requests[3].respond( | ||
200, | ||
{}, | ||
JSON.stringify({ | ||
status: 'published' | ||
}) | ||
); | ||
fauxJax.restore(); | ||
}); | ||
}, 200); | ||
}); |
@@ -5,2 +5,3 @@ var test = require('tape'); | ||
noJSONP(t, 404); | ||
noJSONP(t, 101); | ||
}); | ||
@@ -23,3 +24,3 @@ | ||
function searchCallback(success, content) { | ||
function searchCallback(err) { | ||
t.equal( | ||
@@ -33,8 +34,4 @@ fauxJax.requests.length, | ||
t.notOk(success, 'request failed'); | ||
t.deepEqual(content, { | ||
message: 'No JSONP when ' + statusCode, | ||
status: statusCode | ||
}, 'Content matches'); | ||
t.ok(err instanceof Error, 'err is an Error'); | ||
t.equal(err.message, 'No JSONP when ' + statusCode, 'Error message matches'); | ||
} | ||
@@ -41,0 +38,0 @@ |
@@ -8,2 +8,4 @@ var test = require('tape'); | ||
var createFixture = require('../../utils/create-fixture'); | ||
var ticker = require('../../utils/ticker'); | ||
var fixture = createFixture({ | ||
@@ -31,3 +33,3 @@ clientOptions: { | ||
searchCallback.args[0], | ||
[true, {hosts: 'YES!'}] | ||
[null, {hosts: 'YES!'}] | ||
); | ||
@@ -44,8 +46,17 @@ | ||
fauxJax.requests[0].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
fauxJax.requests[1].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
fauxJax.requests[2].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
fauxJax.requests[3].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
ticker({ | ||
maxTicks: 4, | ||
tickCb: badResponse, | ||
ms: 100, | ||
cb: goodResponse | ||
}); | ||
fauxJax.requests[4].respond(200, {}, JSON.stringify({hosts: 'YES!'})); | ||
function badResponse(tickIndex) { | ||
fauxJax.requests[tickIndex - 1] | ||
.respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
} | ||
function goodResponse() { | ||
fauxJax.requests[4].respond(200, {}, JSON.stringify({hosts: 'YES!'})); | ||
} | ||
}); |
@@ -6,8 +6,9 @@ var test = require('tape'); | ||
test('Request strategy handles slow JSONP responses (no double callback)', function(t) { | ||
var xhr = require('xhr'); | ||
var fauxJax = require('faux-jax'); | ||
var parse = require('url-parse'); | ||
var sinon = require('sinon'); | ||
var xhr = require('xhr'); | ||
var createFixture = require('../../utils/create-fixture'); | ||
var ticker = require('../../utils/ticker'); | ||
@@ -17,9 +18,9 @@ var currentURL = parse(location.href); | ||
clientOptions: { | ||
dsnHost: currentURL.host, | ||
hosts: [ | ||
currentURL.host, | ||
currentURL.host, | ||
currentURL.host, | ||
currentURL.host | ||
], | ||
requestTimeoutInMs: requestTimeout | ||
timeout: requestTimeout | ||
}, | ||
@@ -38,8 +39,5 @@ indexName: 'slow-response' | ||
t.deepEqual( | ||
searchCallback.args[0], [ | ||
true, { | ||
slowResponse: 'ok' | ||
} | ||
], | ||
'Callback called with true, {"slowResponse": "ok"}' | ||
searchCallback.args[0], | ||
[null, {slowResponse: 'ok'}], | ||
'Callback called with null, {"slowResponse": "ok"}' | ||
); | ||
@@ -55,3 +53,2 @@ | ||
}, function run(err) { | ||
t.error(err, 'No error while reseting the /1/indexes/slow-response route'); | ||
@@ -63,7 +60,13 @@ | ||
fauxJax.requests[0].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
fauxJax.requests[1].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
fauxJax.requests[2].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
fauxJax.requests[3].respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
ticker({ | ||
maxTicks: 4, | ||
tickCb: badResponse, | ||
ms: 100 | ||
}); | ||
function badResponse(tickIndex) { | ||
fauxJax.requests[tickIndex - 1] | ||
.respond(500, {}, JSON.stringify({status: 500, message: 'woops!'})); | ||
} | ||
}); | ||
}); |
var test = require('tape'); | ||
var requestTimeout = 2000; | ||
var requestTimeout = 1000; | ||
@@ -11,4 +11,7 @@ test('Request strategy handles slow responses (no double callback)', function(t) { | ||
var clock = sinon.useFakeTimers(); | ||
var fixture = createFixture(); | ||
var fixture = createFixture({ | ||
clientOptions: { | ||
timeout: requestTimeout | ||
} | ||
}); | ||
@@ -25,7 +28,6 @@ var index = fixture.index; | ||
searchCallback.args[0], | ||
[true, {slowResponse: 'ok'}], | ||
'Callback called with true, {"slowResponse": "ok"}' | ||
[null, {slowResponse: 'ok'}], | ||
'Callback called with null, {"slowResponse": "ok"}' | ||
); | ||
clock.restore(); | ||
fauxJax.restore(); | ||
@@ -53,23 +55,23 @@ t.end(); | ||
clock.tick(requestTimeout); | ||
setTimeout(function() { | ||
var secondRequest = fauxJax.requests[1]; | ||
var secondRequest = fauxJax.requests[1]; | ||
t.equal( | ||
fauxJax.requests.length, | ||
2, | ||
'Second request made' | ||
); | ||
t.equal( | ||
fauxJax.requests.length, | ||
2, | ||
'Second request made' | ||
); | ||
firstRequest.respond( | ||
200, | ||
{}, | ||
JSON.stringify({slowResponse: 'timeout response'}) | ||
); | ||
firstRequest.respond( | ||
200, | ||
{}, | ||
JSON.stringify({slowResponse: 'timeout response'}) | ||
); | ||
secondRequest.respond( | ||
200, | ||
{}, | ||
JSON.stringify({slowResponse: 'ok'}) | ||
); | ||
secondRequest.respond( | ||
200, | ||
{}, | ||
JSON.stringify({slowResponse: 'ok'}) | ||
); | ||
}, requestTimeout + requestTimeout / 2); | ||
}); |
@@ -12,3 +12,10 @@ var test = require('tape'); | ||
clientOptions: { | ||
dsnHost: 'yawdsn.com' | ||
hosts: [ | ||
'yawdsn.com', | ||
'booya.com', | ||
'booyou.com', | ||
'booyi.com', | ||
'boolala.com', | ||
'boodibu.com' | ||
] | ||
} | ||
@@ -15,0 +22,0 @@ }); |
@@ -6,3 +6,3 @@ var test = require('tape'); | ||
// this test uses the utils/support-server to get JSONP responses | ||
test('Request strategy uses JSONP when all XHR timed out', function(t) { | ||
test('Request strategy uses JSONP when XHR timedout', function(t) { | ||
var fauxJax = require('faux-jax'); | ||
@@ -14,2 +14,3 @@ var parse = require('url-parse'); | ||
var createFixture = require('../../utils/create-fixture'); | ||
var ticker = require('../../utils/ticker'); | ||
@@ -19,3 +20,2 @@ var currentURL = parse(location.href); | ||
clientOptions: { | ||
dsnHost: currentURL.host, | ||
hosts: [ | ||
@@ -26,3 +26,3 @@ currentURL.host, | ||
], | ||
requestTimeoutInMs: requestTimeout | ||
timeout: requestTimeout | ||
}, | ||
@@ -34,46 +34,38 @@ indexName: 'request-strategy-uses-JSONP' | ||
xhr({ | ||
uri: '/1/indexes/request-strategy-uses-JSONP/reset' | ||
}, function run(err) { | ||
t.error(err, 'No error while reseting the /1/indexes/request-strategy-uses-JSONP route'); | ||
fauxJax.install(); | ||
var searchCallback = sinon.spy(function() { | ||
t.equal(fauxJax.requests.length, 3, 'Three requests made'); | ||
t.ok(searchCallback.calledOnce, 'Callback was called once'); | ||
var clock = sinon.useFakeTimers(); | ||
t.deepEqual( | ||
searchCallback.args[0], | ||
[null, {hello: 'man'}], | ||
'Callback called with null, {"hello": "man"}' | ||
); | ||
var searchCallback = sinon.spy(function() { | ||
t.ok(searchCallback.calledOnce, 'Callback was called once'); | ||
t.deepEqual( | ||
searchCallback.args[0], [ | ||
true, { | ||
hello: 'man' | ||
} | ||
], | ||
'Callback called with true, {"hello": "man"}' | ||
); | ||
fauxJax.restore(); | ||
fauxJax.restore(); | ||
clock.restore(); | ||
t.end(); | ||
}); | ||
t.end(); | ||
}); | ||
xhr({ | ||
uri: '/1/indexes/request-strategy-uses-JSONP/reset' | ||
}, function run(err) { | ||
t.error(err, 'No error while reseting the /1/indexes/request-strategy-uses-JSONP route'); | ||
index.search('hello', searchCallback); | ||
fauxJax.install(); | ||
t.notOk(searchCallback.calledOnce, 'Callback not called on first request'); | ||
t.equal(fauxJax.requests.length, 1, 'One request made'); | ||
t.equal(fauxJax.requests.length, 0, 'No request made'); | ||
index.search('hello', searchCallback); | ||
clock.tick(requestTimeout); | ||
t.equal(fauxJax.requests.length, 2, 'Second requests made'); | ||
t.notOk(searchCallback.calledOnce, 'Callback not called on second request'); | ||
ticker({ | ||
maxTicks: 3, | ||
tickCb: badResponse, | ||
ms: 100 | ||
}); | ||
clock.tick(requestTimeout * 2); | ||
t.equal(fauxJax.requests.length, 3, 'Third requests made'); | ||
t.notOk(searchCallback.calledOnce, 'Callback not called on third request'); | ||
clock.tick(requestTimeout * 3); | ||
t.equal(fauxJax.requests.length, 4, 'Fourth request made'); | ||
t.notOk(searchCallback.calledOnce, 'Callback not called on fourth request'); | ||
clock.tick(requestTimeout * 4); | ||
function badResponse(tickIndex) { | ||
fauxJax.requests[tickIndex - 1] | ||
.respond(500, {}, JSON.stringify({message: 'Try again', status: 500})); | ||
} | ||
}); | ||
}); |
var bulkRequire = require('bulk-require'); | ||
var compression = require('compression'); | ||
var express = require('express'); | ||
@@ -10,2 +11,4 @@ var forEach = require('lodash/collection/forEach'); | ||
app.use(compression()); | ||
app.use(logger('dev')); | ||
@@ -15,2 +18,4 @@ | ||
app.use(express.static(path.join(__dirname, '..', '..'))); | ||
app.use(function noCache(req, res, next) { | ||
@@ -17,0 +22,0 @@ res.set('Cache-Control', 'max-age=0, no-cache'); |
@@ -17,4 +17,5 @@ module.exports = requestStrategyUsesJSONP; | ||
// only reply to the fourth JSONP request | ||
if (calls === 4) { | ||
// only reply to the third JSONP request | ||
// 3 custom hosts, no dsn | ||
if (calls === 3) { | ||
res.jsonp({hello: 'man'}); | ||
@@ -21,0 +22,0 @@ } else { |
@@ -7,3 +7,3 @@ module.exports = slowResponse; | ||
// after the timeout for the first request, we do not do a double callback | ||
var respondAfter = 6000; | ||
var respondAfter = 7000; | ||
@@ -10,0 +10,0 @@ function slowResponse() { |
module.exports = createFixture; | ||
function createFixture(opts) { | ||
var AlgoliaSearch = require('algoliasearch'); | ||
var algoliasearch = require('../../'); | ||
var getCredentials = require('./get-credentials'); | ||
@@ -11,3 +11,3 @@ | ||
var client = new AlgoliaSearch(credentials.applicationID, credentials.searchOnlyAPIKey, opts.clientOptions); | ||
var client = algoliasearch(credentials.applicationID, credentials.searchOnlyAPIKey, opts.clientOptions); | ||
var index = client.initIndex(opts.indexName || credentials.indexName); | ||
@@ -14,0 +14,0 @@ |
@@ -53,5 +53,3 @@ module.exports = runTestCase; | ||
}); | ||
t.end(); | ||
}); | ||
} |
module.exports = testXHRCall; | ||
var AlgoliaSearch = require('algoliasearch'); | ||
var algoliasearch = require('../../'); | ||
var fauxJax = require('faux-jax'); | ||
var parse = require('url-parse'); | ||
var findMethodCallback = require('./find-method-callback'); | ||
var wrapMethodCallback = require('./wrap-method-callback'); | ||
@@ -13,3 +13,3 @@ function testXHRCall(opts) { | ||
var client = new AlgoliaSearch(opts.applicationID, opts.searchOnlyAPIKey); | ||
var client = algoliasearch(opts.applicationID, opts.searchOnlyAPIKey); | ||
var object; | ||
@@ -22,3 +22,6 @@ if (opts.object === 'index') { | ||
var methodCallback = testCase.methodCallback = findMethodCallback(testCase.callArguments); | ||
// we wrap and replace the method callback (index.search('query', cb)) | ||
// so that we have our `checkMethodCallback` called when the callback occured | ||
// as callback are asynchronous, we cannot use synchronous testing | ||
wrapMethodCallback(testCase.callArguments, checkMethodCallback); | ||
@@ -88,16 +91,26 @@ // this needs to be done here to be as close as possible to the new XMLHttpRequest() call | ||
assert.ok( | ||
methodCallback.calledOnce, | ||
'Callback was called once' | ||
); | ||
function checkMethodCallback(methodCallback) { | ||
assert.ok( | ||
methodCallback.calledOnce, | ||
'Callback was called once' | ||
); | ||
var success = testCase.fakeResponse.statusCode === 200 ? true : false; | ||
var error = testCase.fakeResponse.statusCode === 200 ? null : Error; | ||
var args = methodCallback.getCall(0).args; | ||
assert.deepEqual( | ||
methodCallback.getCall(0).args, | ||
[success, testCase.fakeResponse.body], | ||
'Callback called with callback(true, fakeResponse.body)' | ||
); | ||
if (error) { | ||
assert.ok( | ||
args[0] instanceof Error && args.length === 1, | ||
'We received an error and only an error' | ||
); | ||
} else { | ||
assert.deepEqual( | ||
methodCallback.getCall(0).args, | ||
error ? [error] : [error, testCase.fakeResponse.body], | ||
'Callback called with callback(err, res)' | ||
); | ||
} | ||
fauxJax.restore(); | ||
fauxJax.restore(); | ||
} | ||
} | ||
@@ -104,0 +117,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
112
209948
1
4536
595
2
+ Addedes6-promise@2.0.1
+ Addedes6-promise@2.0.1(transitive)