aboutsummaryrefslogtreecommitdiff
path: root/node_modules/algoliasearch/src
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/algoliasearch/src')
-rw-r--r--node_modules/algoliasearch/src/AlgoliaSearch.js669
-rw-r--r--node_modules/algoliasearch/src/AlgoliaSearchCore.js966
-rw-r--r--node_modules/algoliasearch/src/Index.js1249
-rw-r--r--node_modules/algoliasearch/src/IndexBrowser.js39
-rw-r--r--node_modules/algoliasearch/src/IndexCore.js383
-rw-r--r--node_modules/algoliasearch/src/browser/builds/algoliasearch.angular.js201
-rw-r--r--node_modules/algoliasearch/src/browser/builds/algoliasearch.jquery.js147
-rw-r--r--node_modules/algoliasearch/src/browser/builds/algoliasearch.js6
-rw-r--r--node_modules/algoliasearch/src/browser/builds/algoliasearchLite.js6
-rw-r--r--node_modules/algoliasearch/src/browser/createAlgoliasearch.js233
-rw-r--r--node_modules/algoliasearch/src/browser/inline-headers.js15
-rw-r--r--node_modules/algoliasearch/src/browser/jsonp-request.js126
-rw-r--r--node_modules/algoliasearch/src/browser/migration-layer/is-using-latest.js23
-rw-r--r--node_modules/algoliasearch/src/browser/migration-layer/load-v2.js50
-rw-r--r--node_modules/algoliasearch/src/browser/migration-layer/old-globals.js26
-rw-r--r--node_modules/algoliasearch/src/browser/migration-layer/script.js25
-rw-r--r--node_modules/algoliasearch/src/buildSearchMethod.js67
-rw-r--r--node_modules/algoliasearch/src/clone.js3
-rw-r--r--node_modules/algoliasearch/src/createAnalyticsClient.js85
-rw-r--r--node_modules/algoliasearch/src/deprecate.js15
-rw-r--r--node_modules/algoliasearch/src/deprecatedMessage.js7
-rw-r--r--node_modules/algoliasearch/src/errors.js78
-rw-r--r--node_modules/algoliasearch/src/exitPromise.js7
-rw-r--r--node_modules/algoliasearch/src/map.js9
-rw-r--r--node_modules/algoliasearch/src/merge.js19
-rw-r--r--node_modules/algoliasearch/src/omit.js14
-rw-r--r--node_modules/algoliasearch/src/places.js37
-rw-r--r--node_modules/algoliasearch/src/reactnative/builds/algoliasearch.reactnative.js175
-rw-r--r--node_modules/algoliasearch/src/server/builds/AlgoliaSearchServer.js78
-rw-r--r--node_modules/algoliasearch/src/server/builds/get-agent.js107
-rw-r--r--node_modules/algoliasearch/src/server/builds/node.js318
-rw-r--r--node_modules/algoliasearch/src/store.js86
-rw-r--r--node_modules/algoliasearch/src/version.js3
33 files changed, 5272 insertions, 0 deletions
diff --git a/node_modules/algoliasearch/src/AlgoliaSearch.js b/node_modules/algoliasearch/src/AlgoliaSearch.js
new file mode 100644
index 00000000..6dfb4dcc
--- /dev/null
+++ b/node_modules/algoliasearch/src/AlgoliaSearch.js
@@ -0,0 +1,669 @@
+module.exports = AlgoliaSearch;
+
+var Index = require('./Index.js');
+var deprecate = require('./deprecate.js');
+var deprecatedMessage = require('./deprecatedMessage.js');
+var AlgoliaSearchCore = require('./AlgoliaSearchCore.js');
+var inherits = require('inherits');
+var errors = require('./errors');
+
+function AlgoliaSearch() {
+ AlgoliaSearchCore.apply(this, arguments);
+}
+
+inherits(AlgoliaSearch, AlgoliaSearchCore);
+
+/*
+ * Delete an index
+ *
+ * @param indexName the name of index to delete
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer that contains the task ID
+ */
+AlgoliaSearch.prototype.deleteIndex = function(indexName, callback) {
+ return this._jsonRequest({
+ method: 'DELETE',
+ url: '/1/indexes/' + encodeURIComponent(indexName),
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/**
+ * Move an existing index.
+ * @param srcIndexName the name of index to copy.
+ * @param dstIndexName the new index name that will contains a copy of
+ * srcIndexName (destination will be overriten if it already exist).
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer that contains the task ID
+ */
+AlgoliaSearch.prototype.moveIndex = function(srcIndexName, dstIndexName, callback) {
+ var postObj = {
+ operation: 'move', destination: dstIndexName
+ };
+ return this._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/**
+ * Copy an existing index.
+ * @param srcIndexName the name of index to copy.
+ * @param dstIndexName the new index name that will contains a copy
+ * of srcIndexName (destination will be overriten if it already exist).
+ * @param scope an array of scopes to copy: ['settings', 'synonyms', 'rules']
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer that contains the task ID
+ */
+AlgoliaSearch.prototype.copyIndex = function(srcIndexName, dstIndexName, scopeOrCallback, _callback) {
+ var postObj = {
+ operation: 'copy',
+ destination: dstIndexName
+ };
+ var callback = _callback;
+ if (typeof scopeOrCallback === 'function') {
+ // oops, old behaviour of third argument being a function
+ callback = scopeOrCallback;
+ } else if (Array.isArray(scopeOrCallback) && scopeOrCallback.length > 0) {
+ postObj.scope = scopeOrCallback;
+ } else if (typeof scopeOrCallback !== 'undefined') {
+ throw new Error('the scope given to `copyIndex` was not an array with settings, synonyms or rules');
+ }
+ return this._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/**
+ * Return last log entries.
+ * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry).
+ * @param length Specify the maximum number of entries to retrieve starting
+ * at offset. Maximum allowed value: 1000.
+ * @param type Specify the maximum number of entries to retrieve starting
+ * at offset. Maximum allowed value: 1000.
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer that contains the task ID
+ */
+AlgoliaSearch.prototype.getLogs = function(offset, length, callback) {
+ var clone = require('./clone.js');
+ var params = {};
+ if (typeof offset === 'object') {
+ // getLogs(params)
+ params = clone(offset);
+ callback = length;
+ } else if (arguments.length === 0 || typeof offset === 'function') {
+ // getLogs([cb])
+ callback = offset;
+ } else if (arguments.length === 1 || typeof length === 'function') {
+ // getLogs(1, [cb)]
+ callback = length;
+ params.offset = offset;
+ } else {
+ // getLogs(1, 2, [cb])
+ params.offset = offset;
+ params.length = length;
+ }
+
+ if (params.offset === undefined) params.offset = 0;
+ if (params.length === undefined) params.length = 10;
+
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/logs?' + this._getSearchParams(params, ''),
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/*
+ * List all existing indexes (paginated)
+ *
+ * @param page The page to retrieve, starting at 0.
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer with index list
+ */
+AlgoliaSearch.prototype.listIndexes = function(page, callback) {
+ var params = '';
+
+ if (page === undefined || typeof page === 'function') {
+ callback = page;
+ } else {
+ params = '?page=' + page;
+ }
+
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/indexes' + params,
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/*
+ * Get the index object initialized
+ *
+ * @param indexName the name of index
+ * @param callback the result callback with one argument (the Index instance)
+ */
+AlgoliaSearch.prototype.initIndex = function(indexName) {
+ return new Index(this, indexName);
+};
+
+AlgoliaSearch.prototype.initAnalytics = function(opts) {
+ // the actual require must be inside the function, when put outside then you have a cyclic dependency
+ // not well resolved that ends up making the main "./index.js" (main module, the agloliasearch function)
+ // export an object instead of a function
+ // Other workarounds:
+ // - rewrite the lib in ES6, cyclic dependencies may be better supported
+ // - move initAnalytics to a property on the main module (algoliasearch.initAnalytics),
+ // same as places.
+ // The current API was made mostly to mimic the one made in PHP
+ var createAnalyticsClient = require('./createAnalyticsClient.js');
+ return createAnalyticsClient(this.applicationID, this.apiKey, opts);
+};
+
+/*
+ * @deprecated use client.listApiKeys
+ */
+AlgoliaSearch.prototype.listUserKeys = deprecate(function(callback) {
+ return this.listApiKeys(callback);
+}, deprecatedMessage('client.listUserKeys()', 'client.listApiKeys()'));
+
+/*
+ * List all existing api keys with their associated ACLs
+ *
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer with api keys list
+ */
+AlgoliaSearch.prototype.listApiKeys = function(callback) {
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/keys',
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/*
+ * @deprecated see client.getApiKey
+ */
+AlgoliaSearch.prototype.getUserKeyACL = deprecate(function(key, callback) {
+ return this.getApiKey(key, callback);
+}, deprecatedMessage('client.getUserKeyACL()', 'client.getApiKey()'));
+
+/*
+ * Get an API key
+ *
+ * @param key
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer with the right API key
+ */
+AlgoliaSearch.prototype.getApiKey = function(key, callback) {
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/keys/' + key,
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/*
+ * @deprecated see client.deleteApiKey
+ */
+AlgoliaSearch.prototype.deleteUserKey = deprecate(function(key, callback) {
+ return this.deleteApiKey(key, callback);
+}, deprecatedMessage('client.deleteUserKey()', 'client.deleteApiKey()'));
+
+/*
+ * Delete an existing API key
+ * @param key
+ * @param callback the result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer with the date of deletion
+ */
+AlgoliaSearch.prototype.deleteApiKey = function(key, callback) {
+ return this._jsonRequest({
+ method: 'DELETE',
+ url: '/1/keys/' + key,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+ @deprecated see client.addApiKey
+ */
+AlgoliaSearch.prototype.addUserKey = deprecate(function(acls, params, callback) {
+ return this.addApiKey(acls, params, callback);
+}, deprecatedMessage('client.addUserKey()', 'client.addApiKey()'));
+
+/*
+ * Add a new global API key
+ *
+ * @param {string[]} acls - The list of ACL for this key. Defined by an array of strings that
+ * can contains the following values:
+ * - search: allow to search (https and http)
+ * - addObject: allows to add/update an object in the index (https only)
+ * - deleteObject : allows to delete an existing object (https only)
+ * - deleteIndex : allows to delete index content (https only)
+ * - settings : allows to get index settings (https only)
+ * - editSettings : allows to change index settings (https only)
+ * @param {Object} [params] - Optionnal parameters to set for the key
+ * @param {number} params.validity - Number of seconds after which the key will be automatically removed (0 means no time limit for this key)
+ * @param {number} params.maxQueriesPerIPPerHour - Number of API calls allowed from an IP address per hour
+ * @param {number} params.maxHitsPerQuery - Number of hits this API key can retrieve in one call
+ * @param {string[]} params.indexes - Allowed targeted indexes for this key
+ * @param {string} params.description - A description for your key
+ * @param {string[]} params.referers - A list of authorized referers
+ * @param {Object} params.queryParameters - Force the key to use specific query parameters
+ * @param {Function} callback - The result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer with the added API key
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.addApiKey(['search'], {
+ * validity: 300,
+ * maxQueriesPerIPPerHour: 2000,
+ * maxHitsPerQuery: 3,
+ * indexes: ['fruits'],
+ * description: 'Eat three fruits',
+ * referers: ['*.algolia.com'],
+ * queryParameters: {
+ * tagFilters: ['public'],
+ * }
+ * })
+ * @see {@link https://www.algolia.com/doc/rest_api#AddKey|Algolia REST API Documentation}
+ */
+AlgoliaSearch.prototype.addApiKey = function(acls, params, callback) {
+ var isArray = require('isarray');
+ var usage = 'Usage: client.addApiKey(arrayOfAcls[, params, callback])';
+
+ if (!isArray(acls)) {
+ throw new Error(usage);
+ }
+
+ if (arguments.length === 1 || typeof params === 'function') {
+ callback = params;
+ params = null;
+ }
+
+ var postObj = {
+ acl: acls
+ };
+
+ if (params) {
+ postObj.validity = params.validity;
+ postObj.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour;
+ postObj.maxHitsPerQuery = params.maxHitsPerQuery;
+ postObj.indexes = params.indexes;
+ postObj.description = params.description;
+
+ if (params.queryParameters) {
+ postObj.queryParameters = this._getSearchParams(params.queryParameters, '');
+ }
+
+ postObj.referers = params.referers;
+ }
+
+ return this._jsonRequest({
+ method: 'POST',
+ url: '/1/keys',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/**
+ * @deprecated Please use client.addApiKey()
+ */
+AlgoliaSearch.prototype.addUserKeyWithValidity = deprecate(function(acls, params, callback) {
+ return this.addApiKey(acls, params, callback);
+}, deprecatedMessage('client.addUserKeyWithValidity()', 'client.addApiKey()'));
+
+/**
+ * @deprecated Please use client.updateApiKey()
+ */
+AlgoliaSearch.prototype.updateUserKey = deprecate(function(key, acls, params, callback) {
+ return this.updateApiKey(key, acls, params, callback);
+}, deprecatedMessage('client.updateUserKey()', 'client.updateApiKey()'));
+
+/**
+ * Update an existing API key
+ * @param {string} key - The key to update
+ * @param {string[]} acls - The list of ACL for this key. Defined by an array of strings that
+ * can contains the following values:
+ * - search: allow to search (https and http)
+ * - addObject: allows to add/update an object in the index (https only)
+ * - deleteObject : allows to delete an existing object (https only)
+ * - deleteIndex : allows to delete index content (https only)
+ * - settings : allows to get index settings (https only)
+ * - editSettings : allows to change index settings (https only)
+ * @param {Object} [params] - Optionnal parameters to set for the key
+ * @param {number} params.validity - Number of seconds after which the key will be automatically removed (0 means no time limit for this key)
+ * @param {number} params.maxQueriesPerIPPerHour - Number of API calls allowed from an IP address per hour
+ * @param {number} params.maxHitsPerQuery - Number of hits this API key can retrieve in one call
+ * @param {string[]} params.indexes - Allowed targeted indexes for this key
+ * @param {string} params.description - A description for your key
+ * @param {string[]} params.referers - A list of authorized referers
+ * @param {Object} params.queryParameters - Force the key to use specific query parameters
+ * @param {Function} callback - The result callback called with two arguments
+ * error: null or Error('message')
+ * content: the server answer with the modified API key
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.updateApiKey('APIKEY', ['search'], {
+ * validity: 300,
+ * maxQueriesPerIPPerHour: 2000,
+ * maxHitsPerQuery: 3,
+ * indexes: ['fruits'],
+ * description: 'Eat three fruits',
+ * referers: ['*.algolia.com'],
+ * queryParameters: {
+ * tagFilters: ['public'],
+ * }
+ * })
+ * @see {@link https://www.algolia.com/doc/rest_api#UpdateIndexKey|Algolia REST API Documentation}
+ */
+AlgoliaSearch.prototype.updateApiKey = function(key, acls, params, callback) {
+ var isArray = require('isarray');
+ var usage = 'Usage: client.updateApiKey(key, arrayOfAcls[, params, callback])';
+
+ if (!isArray(acls)) {
+ throw new Error(usage);
+ }
+
+ if (arguments.length === 2 || typeof params === 'function') {
+ callback = params;
+ params = null;
+ }
+
+ var putObj = {
+ acl: acls
+ };
+
+ if (params) {
+ putObj.validity = params.validity;
+ putObj.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour;
+ putObj.maxHitsPerQuery = params.maxHitsPerQuery;
+ putObj.indexes = params.indexes;
+ putObj.description = params.description;
+
+ if (params.queryParameters) {
+ putObj.queryParameters = this._getSearchParams(params.queryParameters, '');
+ }
+
+ putObj.referers = params.referers;
+ }
+
+ return this._jsonRequest({
+ method: 'PUT',
+ url: '/1/keys/' + key,
+ body: putObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/**
+ * Initialize a new batch of search queries
+ * @deprecated use client.search()
+ */
+AlgoliaSearch.prototype.startQueriesBatch = deprecate(function startQueriesBatchDeprecated() {
+ this._batch = [];
+}, deprecatedMessage('client.startQueriesBatch()', 'client.search()'));
+
+/**
+ * Add a search query in the batch
+ * @deprecated use client.search()
+ */
+AlgoliaSearch.prototype.addQueryInBatch = deprecate(function addQueryInBatchDeprecated(indexName, query, args) {
+ this._batch.push({
+ indexName: indexName,
+ query: query,
+ params: args
+ });
+}, deprecatedMessage('client.addQueryInBatch()', 'client.search()'));
+
+/**
+ * Launch the batch of queries using XMLHttpRequest.
+ * @deprecated use client.search()
+ */
+AlgoliaSearch.prototype.sendQueriesBatch = deprecate(function sendQueriesBatchDeprecated(callback) {
+ return this.search(this._batch, callback);
+}, deprecatedMessage('client.sendQueriesBatch()', 'client.search()'));
+
+/**
+ * Perform write operations accross multiple indexes.
+ *
+ * To reduce the amount of time spent on network round trips,
+ * you can create, update, or delete several objects in one call,
+ * using the batch endpoint (all operations are done in the given order).
+ *
+ * Available actions:
+ * - addObject
+ * - updateObject
+ * - partialUpdateObject
+ * - partialUpdateObjectNoCreate
+ * - deleteObject
+ *
+ * https://www.algolia.com/doc/rest_api#Indexes
+ * @param {Object[]} operations An array of operations to perform
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.batch([{
+ * action: 'addObject',
+ * indexName: 'clients',
+ * body: {
+ * name: 'Bill'
+ * }
+ * }, {
+ * action: 'udpateObject',
+ * indexName: 'fruits',
+ * body: {
+ * objectID: '29138',
+ * name: 'banana'
+ * }
+ * }], cb)
+ */
+AlgoliaSearch.prototype.batch = function(operations, callback) {
+ var isArray = require('isarray');
+ var usage = 'Usage: client.batch(operations[, callback])';
+
+ if (!isArray(operations)) {
+ throw new Error(usage);
+ }
+
+ return this._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/*/batch',
+ body: {
+ requests: operations
+ },
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/**
+ * Assign or Move a userID to a cluster
+ *
+ * @param {string} data.userID The userID to assign to a new cluster
+ * @param {string} data.cluster The cluster to assign the user to
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.assignUserID({ cluster: 'c1-test', userID: 'some-user' });
+ */
+AlgoliaSearch.prototype.assignUserID = function(data, callback) {
+ if (!data.userID || !data.cluster) {
+ throw new errors.AlgoliaSearchError('You have to provide both a userID and cluster', data);
+ }
+ return this._jsonRequest({
+ method: 'POST',
+ url: '/1/clusters/mapping',
+ hostType: 'write',
+ body: {cluster: data.cluster},
+ callback: callback,
+ headers: {
+ 'x-algolia-user-id': data.userID
+ }
+ });
+};
+
+/**
+ * Get the top userIDs
+ *
+ * (the callback is the second argument)
+ *
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.getTopUserID();
+ */
+AlgoliaSearch.prototype.getTopUserID = function(callback) {
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/clusters/mapping/top',
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/**
+ * Get userID
+ *
+ * @param {string} data.userID The userID to get info about
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.getUserID({ userID: 'some-user' });
+ */
+AlgoliaSearch.prototype.getUserID = function(data, callback) {
+ if (!data.userID) {
+ throw new errors.AlgoliaSearchError('You have to provide a userID', {debugData: data});
+ }
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/clusters/mapping/' + data.userID,
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/**
+ * List all the clusters
+ *
+ * (the callback is the second argument)
+ *
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.listClusters();
+ */
+AlgoliaSearch.prototype.listClusters = function(callback) {
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/clusters',
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/**
+ * List the userIDs
+ *
+ * (the callback is the second argument)
+ *
+ * @param {string} data.hitsPerPage How many hits on every page
+ * @param {string} data.page The page to retrieve
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.listClusters();
+ * client.listClusters({ page: 3, hitsPerPage: 30});
+ */
+AlgoliaSearch.prototype.listUserIDs = function(data, callback) {
+ return this._jsonRequest({
+ method: 'GET',
+ url: '/1/clusters/mapping',
+ body: data,
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/**
+ * Remove an userID
+ *
+ * @param {string} data.userID The userID to assign to a new cluster
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.removeUserID({ userID: 'some-user' });
+ */
+AlgoliaSearch.prototype.removeUserID = function(data, callback) {
+ if (!data.userID) {
+ throw new errors.AlgoliaSearchError('You have to provide a userID', {debugData: data});
+ }
+ return this._jsonRequest({
+ method: 'DELETE',
+ url: '/1/clusters/mapping',
+ hostType: 'write',
+ callback: callback,
+ headers: {
+ 'x-algolia-user-id': data.userID
+ }
+ });
+};
+
+/**
+ * Search for userIDs
+ *
+ * @param {string} data.cluster The cluster to target
+ * @param {string} data.query The query to execute
+ * @param {string} data.hitsPerPage How many hits on every page
+ * @param {string} data.page The page to retrieve
+ * @return {Promise|undefined} Returns a promise if no callback given
+ * @example
+ * client.searchUserIDs({ cluster: 'c1-test', query: 'some-user' });
+ * client.searchUserIDs({
+ * cluster: "c1-test",
+ * query: "some-user",
+ * page: 3,
+ * hitsPerPage: 2
+ * });
+ */
+AlgoliaSearch.prototype.searchUserIDs = function(data, callback) {
+ return this._jsonRequest({
+ method: 'POST',
+ url: '/1/clusters/mapping/search',
+ body: data,
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+// environment specific methods
+AlgoliaSearch.prototype.destroy = notImplemented;
+AlgoliaSearch.prototype.enableRateLimitForward = notImplemented;
+AlgoliaSearch.prototype.disableRateLimitForward = notImplemented;
+AlgoliaSearch.prototype.useSecuredAPIKey = notImplemented;
+AlgoliaSearch.prototype.disableSecuredAPIKey = notImplemented;
+AlgoliaSearch.prototype.generateSecuredApiKey = notImplemented;
+
+function notImplemented() {
+ var message = 'Not implemented in this environment.\n' +
+ 'If you feel this is a mistake, write to support@algolia.com';
+
+ throw new errors.AlgoliaSearchError(message);
+}
diff --git a/node_modules/algoliasearch/src/AlgoliaSearchCore.js b/node_modules/algoliasearch/src/AlgoliaSearchCore.js
new file mode 100644
index 00000000..a94e99ca
--- /dev/null
+++ b/node_modules/algoliasearch/src/AlgoliaSearchCore.js
@@ -0,0 +1,966 @@
+module.exports = AlgoliaSearchCore;
+
+var errors = require('./errors');
+var exitPromise = require('./exitPromise.js');
+var IndexCore = require('./IndexCore.js');
+var store = require('./store.js');
+
+// We will always put the API KEY in the JSON body in case of too long API KEY,
+// to avoid query string being too long and failing in various conditions (our server limit, browser limit,
+// proxies limit)
+var MAX_API_KEY_LENGTH = 500;
+var RESET_APP_DATA_TIMER =
+ process.env.RESET_APP_DATA_TIMER && parseInt(process.env.RESET_APP_DATA_TIMER, 10) ||
+ 60 * 2 * 1000; // after 2 minutes reset to first host
+
+/*
+ * Algolia Search library initialization
+ * 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='https:'] - The protocol used to query Algolia Search API.
+ * Set to 'http:' to force using http.
+ * @param {Object|Array} [opts.hosts={
+ * read: [this.applicationID + '-dsn.algolia.net'].concat([
+ * this.applicationID + '-1.algolianet.com',
+ * this.applicationID + '-2.algolianet.com',
+ * this.applicationID + '-3.algolianet.com']
+ * ]),
+ * write: [this.applicationID + '.algolia.net'].concat([
+ * this.applicationID + '-1.algolianet.com',
+ * this.applicationID + '-2.algolianet.com',
+ * this.applicationID + '-3.algolianet.com']
+ * ]) - The hosts to use for Algolia Search API.
+ * If you provide them, you will less benefit from our HA implementation
+ */
+function AlgoliaSearchCore(applicationID, apiKey, opts) {
+ var debug = require('debug')('algoliasearch');
+
+ var clone = require('./clone.js');
+ var isArray = require('isarray');
+ var map = require('./map.js');
+
+ var usage = 'Usage: algoliasearch(applicationID, apiKey, opts)';
+
+ if (opts._allowEmptyCredentials !== true && !applicationID) {
+ throw new errors.AlgoliaSearchError('Please provide an application ID. ' + usage);
+ }
+
+ if (opts._allowEmptyCredentials !== true && !apiKey) {
+ throw new errors.AlgoliaSearchError('Please provide an API key. ' + usage);
+ }
+
+ this.applicationID = applicationID;
+ this.apiKey = apiKey;
+
+ this.hosts = {
+ read: [],
+ write: []
+ };
+
+ opts = opts || {};
+
+ this._timeouts = opts.timeouts || {
+ connect: 1 * 1000, // 500ms connect is GPRS latency
+ read: 2 * 1000,
+ write: 30 * 1000
+ };
+
+ // backward compat, if opts.timeout is passed, we use it to configure all timeouts like before
+ if (opts.timeout) {
+ this._timeouts.connect = this._timeouts.read = this._timeouts.write = opts.timeout;
+ }
+
+ var protocol = opts.protocol || 'https:';
+ // 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(protocol)) {
+ protocol = protocol + ':';
+ }
+
+ if (protocol !== 'http:' && protocol !== 'https:') {
+ throw new errors.AlgoliaSearchError('protocol must be `http:` or `https:` (was `' + opts.protocol + '`)');
+ }
+
+ this._checkAppIdData();
+
+ if (!opts.hosts) {
+ var defaultHosts = map(this._shuffleResult, function(hostNumber) {
+ return applicationID + '-' + hostNumber + '.algolianet.com';
+ });
+
+ // no hosts given, compute defaults
+ var mainSuffix = (opts.dsn === false ? '' : '-dsn') + '.algolia.net';
+ this.hosts.read = [this.applicationID + mainSuffix].concat(defaultHosts);
+ this.hosts.write = [this.applicationID + '.algolia.net'].concat(defaultHosts);
+ } else if (isArray(opts.hosts)) {
+ // when passing custom hosts, we need to have a different host index if the number
+ // of write/read hosts are different.
+ this.hosts.read = clone(opts.hosts);
+ this.hosts.write = clone(opts.hosts);
+ } else {
+ this.hosts.read = clone(opts.hosts.read);
+ this.hosts.write = clone(opts.hosts.write);
+ }
+
+ // add protocol and lowercase hosts
+ this.hosts.read = map(this.hosts.read, prepareHost(protocol));
+ this.hosts.write = map(this.hosts.write, prepareHost(protocol));
+
+ this.extraHeaders = {};
+
+ // In some situations you might want to warm the cache
+ this.cache = opts._cache || {};
+
+ this._ua = opts._ua;
+ this._useCache = opts._useCache === undefined || opts._cache ? true : opts._useCache;
+ this._useRequestCache = this._useCache && opts._useRequestCache;
+ this._useFallback = opts.useFallback === undefined ? true : opts.useFallback;
+
+ this._setTimeout = opts._setTimeout;
+
+ debug('init done, %j', this);
+}
+
+/*
+ * Get the index object initialized
+ *
+ * @param indexName the name of index
+ * @param callback the result callback with one argument (the Index instance)
+ */
+AlgoliaSearchCore.prototype.initIndex = function(indexName) {
+ return new IndexCore(this, indexName);
+};
+
+/**
+* Add an extra field to the HTTP request
+*
+* @param name the header field name
+* @param value the header field value
+*/
+AlgoliaSearchCore.prototype.setExtraHeader = function(name, value) {
+ this.extraHeaders[name.toLowerCase()] = value;
+};
+
+/**
+* Get the value of an extra HTTP header
+*
+* @param name the header field name
+*/
+AlgoliaSearchCore.prototype.getExtraHeader = function(name) {
+ return this.extraHeaders[name.toLowerCase()];
+};
+
+/**
+* Remove an extra field from the HTTP request
+*
+* @param name the header field name
+*/
+AlgoliaSearchCore.prototype.unsetExtraHeader = function(name) {
+ delete this.extraHeaders[name.toLowerCase()];
+};
+
+/**
+* Augment sent x-algolia-agent with more data, each agent part
+* is automatically separated from the others by a semicolon;
+*
+* @param algoliaAgent the agent to add
+*/
+AlgoliaSearchCore.prototype.addAlgoliaAgent = function(algoliaAgent) {
+ if (this._ua.indexOf(';' + algoliaAgent) === -1) {
+ this._ua += ';' + algoliaAgent;
+ }
+};
+
+/*
+ * Wrapper that try all hosts to maximize the quality of service
+ */
+AlgoliaSearchCore.prototype._jsonRequest = function(initialOpts) {
+ this._checkAppIdData();
+
+ var requestDebug = require('debug')('algoliasearch:' + initialOpts.url);
+
+
+ var body;
+ var cacheID;
+ var additionalUA = initialOpts.additionalUA || '';
+ var cache = initialOpts.cache;
+ var client = this;
+ var tries = 0;
+ var usingFallback = false;
+ var hasFallback = client._useFallback && client._request.fallback && initialOpts.fallback;
+ var headers;
+
+ if (
+ this.apiKey.length > MAX_API_KEY_LENGTH &&
+ initialOpts.body !== undefined &&
+ (initialOpts.body.params !== undefined || // index.search()
+ initialOpts.body.requests !== undefined) // client.search()
+ ) {
+ initialOpts.body.apiKey = this.apiKey;
+ headers = this._computeRequestHeaders({
+ additionalUA: additionalUA,
+ withApiKey: false,
+ headers: initialOpts.headers
+ });
+ } else {
+ headers = this._computeRequestHeaders({
+ additionalUA: additionalUA,
+ headers: initialOpts.headers
+ });
+ }
+
+ if (initialOpts.body !== undefined) {
+ body = safeJSONStringify(initialOpts.body);
+ }
+
+ requestDebug('request start');
+ var debugData = [];
+
+
+ function doRequest(requester, reqOpts) {
+ client._checkAppIdData();
+
+ var startTime = new Date();
+
+ if (client._useCache && !client._useRequestCache) {
+ cacheID = initialOpts.url;
+ }
+
+ // as we sometime use POST requests to pass parameters (like query='aa'),
+ // the cacheID must also include the body to be different between calls
+ if (client._useCache && !client._useRequestCache && body) {
+ cacheID += '_body_' + reqOpts.body;
+ }
+
+ // handle cache existence
+ if (isCacheValidWithCurrentID(!client._useRequestCache, cache, cacheID)) {
+ requestDebug('serving response from cache');
+
+ var responseText = cache[cacheID];
+
+ // Cache response must match the type of the original one
+ return client._promise.resolve({
+ body: JSON.parse(responseText),
+ responseText: responseText
+ });
+ }
+
+ // if we reached max tries
+ if (tries >= client.hosts[initialOpts.hostType].length) {
+ if (!hasFallback || usingFallback) {
+ requestDebug('could not get any response');
+ // then stop
+ return client._promise.reject(new errors.AlgoliaSearchError(
+ 'Cannot connect to the AlgoliaSearch API.' +
+ ' Send an email to support@algolia.com to report and resolve the issue.' +
+ ' Application id was: ' + client.applicationID, {debugData: debugData}
+ ));
+ }
+
+ requestDebug('switching to fallback');
+
+ // let's try the fallback starting from here
+ tries = 0;
+
+ // method, url and body are fallback dependent
+ reqOpts.method = initialOpts.fallback.method;
+ reqOpts.url = initialOpts.fallback.url;
+ reqOpts.jsonBody = initialOpts.fallback.body;
+ if (reqOpts.jsonBody) {
+ reqOpts.body = safeJSONStringify(reqOpts.jsonBody);
+ }
+ // re-compute headers, they could be omitting the API KEY
+ headers = client._computeRequestHeaders({
+ additionalUA: additionalUA,
+ headers: initialOpts.headers
+ });
+
+ reqOpts.timeouts = client._getTimeoutsForRequest(initialOpts.hostType);
+ client._setHostIndexByType(0, initialOpts.hostType);
+ usingFallback = true; // the current request is now using fallback
+ return doRequest(client._request.fallback, reqOpts);
+ }
+
+ var currentHost = client._getHostByType(initialOpts.hostType);
+
+ var url = currentHost + reqOpts.url;
+ var options = {
+ body: reqOpts.body,
+ jsonBody: reqOpts.jsonBody,
+ method: reqOpts.method,
+ headers: headers,
+ timeouts: reqOpts.timeouts,
+ debug: requestDebug,
+ forceAuthHeaders: reqOpts.forceAuthHeaders
+ };
+
+ requestDebug('method: %s, url: %s, headers: %j, timeouts: %d',
+ options.method, url, options.headers, options.timeouts);
+
+ if (requester === client._request.fallback) {
+ requestDebug('using fallback');
+ }
+
+ // `requester` is any of this._request or this._request.fallback
+ // thus it needs to be called using the client as context
+ return requester.call(client, url, options).then(success, tryFallback);
+
+ function success(httpResponse) {
+ // compute the status of the response,
+ //
+ // When in browser mode, using XDR or JSONP, we have no statusCode available
+ // So we rely on our API response `status` property.
+ // But `waitTask` can set a `status` property which is not the statusCode (it's the task status)
+ // So we check if there's a `message` along `status` and it means it's an error
+ //
+ // That's the only case where we have a response.status that's not the http statusCode
+ var status = httpResponse && httpResponse.body && httpResponse.body.message && httpResponse.body.status ||
+
+ // 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 ||
+
+ // 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;
+
+ requestDebug('received response: statusCode: %s, computed statusCode: %d, headers: %j',
+ httpResponse.statusCode, status, httpResponse.headers);
+
+ var httpResponseOk = Math.floor(status / 100) === 2;
+
+ var endTime = new Date();
+ debugData.push({
+ currentHost: currentHost,
+ headers: removeCredentials(headers),
+ content: body || null,
+ contentLength: body !== undefined ? body.length : null,
+ method: reqOpts.method,
+ timeouts: reqOpts.timeouts,
+ url: reqOpts.url,
+ startTime: startTime,
+ endTime: endTime,
+ duration: endTime - startTime,
+ statusCode: status
+ });
+
+ if (httpResponseOk) {
+ if (client._useCache && !client._useRequestCache && cache) {
+ cache[cacheID] = httpResponse.responseText;
+ }
+
+ return {
+ responseText: httpResponse.responseText,
+ body: httpResponse.body
+ };
+ }
+
+ var shouldRetry = Math.floor(status / 100) !== 4;
+
+ if (shouldRetry) {
+ tries += 1;
+ return retryRequest();
+ }
+
+ requestDebug('unrecoverable error');
+
+ // no success and no retry => fail
+ var unrecoverableError = new errors.AlgoliaSearchError(
+ httpResponse.body && httpResponse.body.message, {debugData: debugData, statusCode: status}
+ );
+
+ return client._promise.reject(unrecoverableError);
+ }
+
+ function tryFallback(err) {
+ // error cases:
+ // While not in fallback mode:
+ // - CORS not supported
+ // - network error
+ // While in fallback mode:
+ // - timeout
+ // - network error
+ // - badly formatted JSONP (script loaded, did not call our callback)
+ // In both cases:
+ // - uncaught exception occurs (TypeError)
+ requestDebug('error: %s, stack: %s', err.message, err.stack);
+
+ var endTime = new Date();
+ debugData.push({
+ currentHost: currentHost,
+ headers: removeCredentials(headers),
+ content: body || null,
+ contentLength: body !== undefined ? body.length : null,
+ method: reqOpts.method,
+ timeouts: reqOpts.timeouts,
+ url: reqOpts.url,
+ startTime: startTime,
+ endTime: endTime,
+ duration: endTime - startTime
+ });
+
+ if (!(err instanceof errors.AlgoliaSearchError)) {
+ err = new errors.Unknown(err && err.message, err);
+ }
+
+ tries += 1;
+
+ // stop the request implementation when:
+ if (
+ // we did not generate this error,
+ // it comes from a throw in some other piece of code
+ err instanceof errors.Unknown ||
+
+ // server sent unparsable JSON
+ err instanceof errors.UnparsableJSON ||
+
+ // max tries and already using fallback or no fallback
+ tries >= client.hosts[initialOpts.hostType].length &&
+ (usingFallback || !hasFallback)) {
+ // stop request implementation for this command
+ err.debugData = debugData;
+ return client._promise.reject(err);
+ }
+
+ // When a timeout occured, retry by raising timeout
+ if (err instanceof errors.RequestTimeout) {
+ return retryRequestWithHigherTimeout();
+ }
+
+ return retryRequest();
+ }
+
+ function retryRequest() {
+ requestDebug('retrying request');
+ client._incrementHostIndex(initialOpts.hostType);
+ return doRequest(requester, reqOpts);
+ }
+
+ function retryRequestWithHigherTimeout() {
+ requestDebug('retrying request with higher timeout');
+ client._incrementHostIndex(initialOpts.hostType);
+ client._incrementTimeoutMultipler();
+ reqOpts.timeouts = client._getTimeoutsForRequest(initialOpts.hostType);
+ return doRequest(requester, reqOpts);
+ }
+ }
+
+ function isCacheValidWithCurrentID(
+ useRequestCache,
+ currentCache,
+ currentCacheID
+ ) {
+ return (
+ client._useCache &&
+ useRequestCache &&
+ currentCache &&
+ currentCache[currentCacheID] !== undefined
+ );
+ }
+
+
+ function interopCallbackReturn(request, callback) {
+ if (isCacheValidWithCurrentID(client._useRequestCache, cache, cacheID)) {
+ request.catch(function() {
+ // Release the cache on error
+ delete cache[cacheID];
+ });
+ }
+
+ if (typeof initialOpts.callback === 'function') {
+ // either we have a callback
+ request.then(function okCb(content) {
+ exitPromise(function() {
+ initialOpts.callback(null, callback(content));
+ }, client._setTimeout || setTimeout);
+ }, function nookCb(err) {
+ exitPromise(function() {
+ initialOpts.callback(err);
+ }, client._setTimeout || setTimeout);
+ });
+ } else {
+ // either we are using promises
+ return request.then(callback);
+ }
+ }
+
+ if (client._useCache && client._useRequestCache) {
+ cacheID = initialOpts.url;
+ }
+
+ // as we sometime use POST requests to pass parameters (like query='aa'),
+ // the cacheID must also include the body to be different between calls
+ if (client._useCache && client._useRequestCache && body) {
+ cacheID += '_body_' + body;
+ }
+
+ if (isCacheValidWithCurrentID(client._useRequestCache, cache, cacheID)) {
+ requestDebug('serving request from cache');
+
+ var maybePromiseForCache = cache[cacheID];
+
+ // In case the cache is warmup with value that is not a promise
+ var promiseForCache = typeof maybePromiseForCache.then !== 'function'
+ ? client._promise.resolve({responseText: maybePromiseForCache})
+ : maybePromiseForCache;
+
+ return interopCallbackReturn(promiseForCache, function(content) {
+ // In case of the cache request, return the original value
+ return JSON.parse(content.responseText);
+ });
+ }
+
+ var request = doRequest(
+ client._request, {
+ url: initialOpts.url,
+ method: initialOpts.method,
+ body: body,
+ jsonBody: initialOpts.body,
+ timeouts: client._getTimeoutsForRequest(initialOpts.hostType),
+ forceAuthHeaders: initialOpts.forceAuthHeaders
+ }
+ );
+
+ if (client._useCache && client._useRequestCache && cache) {
+ cache[cacheID] = request;
+ }
+
+ return interopCallbackReturn(request, function(content) {
+ // In case of the first request, return the JSON value
+ return content.body;
+ });
+};
+
+/*
+* Transform search param object in query string
+* @param {object} args arguments to add to the current query string
+* @param {string} params current query string
+* @return {string} the final query string
+*/
+AlgoliaSearchCore.prototype._getSearchParams = function(args, params) {
+ if (args === undefined || args === null) {
+ return params;
+ }
+ for (var key in args) {
+ if (key !== null && args[key] !== undefined && args.hasOwnProperty(key)) {
+ params += params === '' ? '' : '&';
+ params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? safeJSONStringify(args[key]) : args[key]);
+ }
+ }
+ return params;
+};
+
+/**
+ * Compute the headers for a request
+ *
+ * @param [string] options.additionalUA semi-colon separated string with other user agents to add
+ * @param [boolean=true] options.withApiKey Send the api key as a header
+ * @param [Object] options.headers Extra headers to send
+ */
+AlgoliaSearchCore.prototype._computeRequestHeaders = function(options) {
+ var forEach = require('foreach');
+
+ var ua = options.additionalUA ?
+ this._ua + ';' + options.additionalUA :
+ this._ua;
+
+ var requestHeaders = {
+ 'x-algolia-agent': ua,
+ 'x-algolia-application-id': this.applicationID
+ };
+
+ // browser will inline headers in the url, node.js will use http headers
+ // but in some situations, the API KEY will be too long (big secured API keys)
+ // so if the request is a POST and the KEY is very long, we will be asked to not put
+ // it into headers but in the JSON body
+ if (options.withApiKey !== false) {
+ requestHeaders['x-algolia-api-key'] = this.apiKey;
+ }
+
+ if (this.userToken) {
+ requestHeaders['x-algolia-usertoken'] = this.userToken;
+ }
+
+ if (this.securityTags) {
+ requestHeaders['x-algolia-tagfilters'] = this.securityTags;
+ }
+
+ forEach(this.extraHeaders, function addToRequestHeaders(value, key) {
+ requestHeaders[key] = value;
+ });
+
+ if (options.headers) {
+ forEach(options.headers, function addToRequestHeaders(value, key) {
+ requestHeaders[key] = value;
+ });
+ }
+
+ return requestHeaders;
+};
+
+/**
+ * Search through multiple indices at the same time
+ * @param {Object[]} queries An array of queries you want to run.
+ * @param {string} queries[].indexName The index name you want to target
+ * @param {string} [queries[].query] The query to issue on this index. Can also be passed into `params`
+ * @param {Object} queries[].params Any search param like hitsPerPage, ..
+ * @param {Function} callback Callback to be called
+ * @return {Promise|undefined} Returns a promise if no callback given
+ */
+AlgoliaSearchCore.prototype.search = function(queries, opts, callback) {
+ var isArray = require('isarray');
+ var map = require('./map.js');
+
+ var usage = 'Usage: client.search(arrayOfQueries[, callback])';
+
+ if (!isArray(queries)) {
+ throw new Error(usage);
+ }
+
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ var client = this;
+
+ var postObj = {
+ requests: map(queries, function prepareRequest(query) {
+ var params = '';
+
+ // allow query.query
+ // so we are mimicing the index.search(query, params) method
+ // {indexName:, query:, params:}
+ if (query.query !== undefined) {
+ params += 'query=' + encodeURIComponent(query.query);
+ }
+
+ return {
+ indexName: query.indexName,
+ params: client._getSearchParams(query.params, params)
+ };
+ })
+ };
+
+ var JSONPParams = map(postObj.requests, function prepareJSONPParams(request, requestId) {
+ return requestId + '=' +
+ encodeURIComponent(
+ '/1/indexes/' + encodeURIComponent(request.indexName) + '?' +
+ request.params
+ );
+ }).join('&');
+
+ var url = '/1/indexes/*/queries';
+
+ if (opts.strategy !== undefined) {
+ postObj.strategy = opts.strategy;
+ }
+
+ return this._jsonRequest({
+ cache: this.cache,
+ method: 'POST',
+ url: url,
+ body: postObj,
+ hostType: 'read',
+ fallback: {
+ method: 'GET',
+ url: '/1/indexes/*',
+ body: {
+ params: JSONPParams
+ }
+ },
+ callback: callback
+ });
+};
+
+/**
+* Search for facet values
+* https://www.algolia.com/doc/rest-api/search#search-for-facet-values
+* This is the top-level API for SFFV.
+*
+* @param {object[]} queries An array of queries to run.
+* @param {string} queries[].indexName Index name, name of the index to search.
+* @param {object} queries[].params Query parameters.
+* @param {string} queries[].params.facetName Facet name, name of the attribute to search for values in.
+* Must be declared as a facet
+* @param {string} queries[].params.facetQuery Query for the facet search
+* @param {string} [queries[].params.*] Any search parameter of Algolia,
+* see https://www.algolia.com/doc/api-client/javascript/search#search-parameters
+* Pagination is not supported. The page and hitsPerPage parameters will be ignored.
+*/
+AlgoliaSearchCore.prototype.searchForFacetValues = function(queries) {
+ var isArray = require('isarray');
+ var map = require('./map.js');
+
+ var usage = 'Usage: client.searchForFacetValues([{indexName, params: {facetName, facetQuery, ...params}}, ...queries])'; // eslint-disable-line max-len
+
+ if (!isArray(queries)) {
+ throw new Error(usage);
+ }
+
+ var client = this;
+
+ return client._promise.all(map(queries, function performQuery(query) {
+ if (
+ !query ||
+ query.indexName === undefined ||
+ query.params.facetName === undefined ||
+ query.params.facetQuery === undefined
+ ) {
+ throw new Error(usage);
+ }
+
+ var clone = require('./clone.js');
+ var omit = require('./omit.js');
+
+ var indexName = query.indexName;
+ var params = query.params;
+
+ var facetName = params.facetName;
+ var filteredParams = omit(clone(params), function(keyName) {
+ return keyName === 'facetName';
+ });
+ var searchParameters = client._getSearchParams(filteredParams, '');
+
+ return client._jsonRequest({
+ cache: client.cache,
+ method: 'POST',
+ url:
+ '/1/indexes/' +
+ encodeURIComponent(indexName) +
+ '/facets/' +
+ encodeURIComponent(facetName) +
+ '/query',
+ hostType: 'read',
+ body: {params: searchParameters}
+ });
+ }));
+};
+
+/**
+ * Set the extra security tagFilters header
+ * @param {string|array} tags The list of tags defining the current security filters
+ */
+AlgoliaSearchCore.prototype.setSecurityTags = function(tags) {
+ if (Object.prototype.toString.call(tags) === '[object Array]') {
+ var strTags = [];
+ for (var i = 0; i < tags.length; ++i) {
+ if (Object.prototype.toString.call(tags[i]) === '[object Array]') {
+ var oredTags = [];
+ for (var j = 0; j < tags[i].length; ++j) {
+ oredTags.push(tags[i][j]);
+ }
+ strTags.push('(' + oredTags.join(',') + ')');
+ } else {
+ strTags.push(tags[i]);
+ }
+ }
+ tags = strTags.join(',');
+ }
+
+ this.securityTags = tags;
+};
+
+/**
+ * Set the extra user token header
+ * @param {string} userToken The token identifying a uniq user (used to apply rate limits)
+ */
+AlgoliaSearchCore.prototype.setUserToken = function(userToken) {
+ this.userToken = userToken;
+};
+
+/**
+ * Clear all queries in client's cache
+ * @return undefined
+ */
+AlgoliaSearchCore.prototype.clearCache = function() {
+ this.cache = {};
+};
+
+/**
+* Set the number of milliseconds a request can take before automatically being terminated.
+* @deprecated
+* @param {Number} milliseconds
+*/
+AlgoliaSearchCore.prototype.setRequestTimeout = function(milliseconds) {
+ if (milliseconds) {
+ this._timeouts.connect = this._timeouts.read = this._timeouts.write = milliseconds;
+ }
+};
+
+/**
+* Set the three different (connect, read, write) timeouts to be used when requesting
+* @param {Object} timeouts
+*/
+AlgoliaSearchCore.prototype.setTimeouts = function(timeouts) {
+ this._timeouts = timeouts;
+};
+
+/**
+* Get the three different (connect, read, write) timeouts to be used when requesting
+* @param {Object} timeouts
+*/
+AlgoliaSearchCore.prototype.getTimeouts = function() {
+ return this._timeouts;
+};
+
+AlgoliaSearchCore.prototype._getAppIdData = function() {
+ var data = store.get(this.applicationID);
+ if (data !== null) this._cacheAppIdData(data);
+ return data;
+};
+
+AlgoliaSearchCore.prototype._setAppIdData = function(data) {
+ data.lastChange = (new Date()).getTime();
+ this._cacheAppIdData(data);
+ return store.set(this.applicationID, data);
+};
+
+AlgoliaSearchCore.prototype._checkAppIdData = function() {
+ var data = this._getAppIdData();
+ var now = (new Date()).getTime();
+ if (data === null || now - data.lastChange > RESET_APP_DATA_TIMER) {
+ return this._resetInitialAppIdData(data);
+ }
+
+ return data;
+};
+
+AlgoliaSearchCore.prototype._resetInitialAppIdData = function(data) {
+ var newData = data || {};
+ newData.hostIndexes = {read: 0, write: 0};
+ newData.timeoutMultiplier = 1;
+ newData.shuffleResult = newData.shuffleResult || shuffle([1, 2, 3]);
+ return this._setAppIdData(newData);
+};
+
+AlgoliaSearchCore.prototype._cacheAppIdData = function(data) {
+ this._hostIndexes = data.hostIndexes;
+ this._timeoutMultiplier = data.timeoutMultiplier;
+ this._shuffleResult = data.shuffleResult;
+};
+
+AlgoliaSearchCore.prototype._partialAppIdDataUpdate = function(newData) {
+ var foreach = require('foreach');
+ var currentData = this._getAppIdData();
+ foreach(newData, function(value, key) {
+ currentData[key] = value;
+ });
+
+ return this._setAppIdData(currentData);
+};
+
+AlgoliaSearchCore.prototype._getHostByType = function(hostType) {
+ return this.hosts[hostType][this._getHostIndexByType(hostType)];
+};
+
+AlgoliaSearchCore.prototype._getTimeoutMultiplier = function() {
+ return this._timeoutMultiplier;
+};
+
+AlgoliaSearchCore.prototype._getHostIndexByType = function(hostType) {
+ return this._hostIndexes[hostType];
+};
+
+AlgoliaSearchCore.prototype._setHostIndexByType = function(hostIndex, hostType) {
+ var clone = require('./clone');
+ var newHostIndexes = clone(this._hostIndexes);
+ newHostIndexes[hostType] = hostIndex;
+ this._partialAppIdDataUpdate({hostIndexes: newHostIndexes});
+ return hostIndex;
+};
+
+AlgoliaSearchCore.prototype._incrementHostIndex = function(hostType) {
+ return this._setHostIndexByType(
+ (this._getHostIndexByType(hostType) + 1) % this.hosts[hostType].length, hostType
+ );
+};
+
+AlgoliaSearchCore.prototype._incrementTimeoutMultipler = function() {
+ var timeoutMultiplier = Math.max(this._timeoutMultiplier + 1, 4);
+ return this._partialAppIdDataUpdate({timeoutMultiplier: timeoutMultiplier});
+};
+
+AlgoliaSearchCore.prototype._getTimeoutsForRequest = function(hostType) {
+ return {
+ connect: this._timeouts.connect * this._timeoutMultiplier,
+ complete: this._timeouts[hostType] * this._timeoutMultiplier
+ };
+};
+
+function prepareHost(protocol) {
+ return function prepare(host) {
+ return protocol + '//' + host.toLowerCase();
+ };
+}
+
+// Prototype.js < 1.7, a widely used library, defines a weird
+// Array.prototype.toJSON function that will fail to stringify our content
+// appropriately
+// refs:
+// - https://groups.google.com/forum/#!topic/prototype-core/E-SAVvV_V9Q
+// - https://github.com/sstephenson/prototype/commit/038a2985a70593c1a86c230fadbdfe2e4898a48c
+// - http://stackoverflow.com/a/3148441/147079
+function safeJSONStringify(obj) {
+ /* eslint no-extend-native:0 */
+
+ if (Array.prototype.toJSON === undefined) {
+ return JSON.stringify(obj);
+ }
+
+ var toJSON = Array.prototype.toJSON;
+ delete Array.prototype.toJSON;
+ var out = JSON.stringify(obj);
+ Array.prototype.toJSON = toJSON;
+
+ return out;
+}
+
+function shuffle(array) {
+ var currentIndex = array.length;
+ var temporaryValue;
+ var randomIndex;
+
+ // While there remain elements to shuffle...
+ while (currentIndex !== 0) {
+ // 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;
+}
+
+function removeCredentials(headers) {
+ var newHeaders = {};
+
+ for (var headerName in headers) {
+ if (Object.prototype.hasOwnProperty.call(headers, headerName)) {
+ var value;
+
+ if (headerName === 'x-algolia-api-key' || headerName === 'x-algolia-application-id') {
+ value = '**hidden for security purposes**';
+ } else {
+ value = headers[headerName];
+ }
+
+ newHeaders[headerName] = value;
+ }
+ }
+
+ return newHeaders;
+}
diff --git a/node_modules/algoliasearch/src/Index.js b/node_modules/algoliasearch/src/Index.js
new file mode 100644
index 00000000..99e5a196
--- /dev/null
+++ b/node_modules/algoliasearch/src/Index.js
@@ -0,0 +1,1249 @@
+var inherits = require('inherits');
+var IndexCore = require('./IndexCore.js');
+var deprecate = require('./deprecate.js');
+var deprecatedMessage = require('./deprecatedMessage.js');
+var exitPromise = require('./exitPromise.js');
+var errors = require('./errors');
+
+var deprecateForwardToSlaves = deprecate(
+ function() {},
+ deprecatedMessage('forwardToSlaves', 'forwardToReplicas')
+);
+
+module.exports = Index;
+
+function Index() {
+ IndexCore.apply(this, arguments);
+}
+
+inherits(Index, IndexCore);
+
+/*
+* Add an object in this index
+*
+* @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 called with two arguments:
+* error: null or Error('message')
+* content: the server answer that contains 3 elements: createAt, taskId and objectID
+*/
+Index.prototype.addObject = function(content, objectID, callback) {
+ var indexObj = this;
+
+ 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,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Add several objects
+*
+* @param objects contains an array of objects to add
+* @param callback (optional) the result callback called with two arguments:
+* error: null or Error('message')
+* content: the server answer that updateAt and taskID
+*/
+Index.prototype.addObjects = function(objects, callback) {
+ var isArray = require('isarray');
+ var usage = 'Usage: index.addObjects(arrayOfObjects[, callback])';
+
+ if (!isArray(objects)) {
+ throw new Error(usage);
+ }
+
+ var indexObj = this;
+ var postObj = {
+ requests: []
+ };
+ for (var i = 0; i < objects.length; ++i) {
+ var request = {
+ action: 'addObject',
+ body: objects[i]
+ };
+ postObj.requests.push(request);
+ }
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Update partially an object (only update attributes passed in argument)
+*
+* @param partialObject contains the javascript attributes to override, the
+* object must contains an objectID attribute
+* @param createIfNotExists (optional) if false, avoid an automatic creation of the object
+* @param callback (optional) the result callback called with two arguments:
+* error: null or Error('message')
+* content: the server answer that contains 3 elements: createAt, taskId and objectID
+*/
+Index.prototype.partialUpdateObject = function(partialObject, createIfNotExists, callback) {
+ if (arguments.length === 1 || typeof createIfNotExists === 'function') {
+ callback = createIfNotExists;
+ createIfNotExists = undefined;
+ }
+
+ var indexObj = this;
+ var url = '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial';
+ if (createIfNotExists === false) {
+ url += '?createIfNotExists=false';
+ }
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: url,
+ body: partialObject,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Partially Override the content of several objects
+*
+* @param objects contains an array of objects to update (each object must contains a objectID attribute)
+* @param callback (optional) the result callback called with two arguments:
+* error: null or Error('message')
+* content: the server answer that updateAt and taskID
+*/
+Index.prototype.partialUpdateObjects = function(objects, createIfNotExists, callback) {
+ if (arguments.length === 1 || typeof createIfNotExists === 'function') {
+ callback = createIfNotExists;
+ createIfNotExists = true;
+ }
+
+ var isArray = require('isarray');
+ var usage = 'Usage: index.partialUpdateObjects(arrayOfObjects[, callback])';
+
+ if (!isArray(objects)) {
+ throw new Error(usage);
+ }
+
+ var indexObj = this;
+ var postObj = {
+ requests: []
+ };
+ for (var i = 0; i < objects.length; ++i) {
+ var request = {
+ action: createIfNotExists === true ? 'partialUpdateObject' : 'partialUpdateObjectNoCreate',
+ objectID: objects[i].objectID,
+ body: objects[i]
+ };
+ postObj.requests.push(request);
+ }
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Override the content of object
+*
+* @param object contains the javascript object to save, the object must contains an objectID attribute
+* @param callback (optional) the result callback called with two arguments:
+* error: null or Error('message')
+* content: the server answer that updateAt and taskID
+*/
+Index.prototype.saveObject = function(object, callback) {
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'PUT',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID),
+ body: object,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Override the content of several objects
+*
+* @param objects contains an array of objects to update (each object must contains a objectID attribute)
+* @param callback (optional) the result callback called with two arguments:
+* error: null or Error('message')
+* content: the server answer that updateAt and taskID
+*/
+Index.prototype.saveObjects = function(objects, callback) {
+ var isArray = require('isarray');
+ var usage = 'Usage: index.saveObjects(arrayOfObjects[, callback])';
+
+ if (!isArray(objects)) {
+ throw new Error(usage);
+ }
+
+ var indexObj = this;
+ var postObj = {
+ requests: []
+ };
+ for (var i = 0; i < objects.length; ++i) {
+ var request = {
+ action: 'updateObject',
+ objectID: objects[i].objectID,
+ body: objects[i]
+ };
+ postObj.requests.push(request);
+ }
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Delete an object from the index
+*
+* @param objectID the unique identifier of object to delete
+* @param callback (optional) the result callback called with two arguments:
+* error: null or Error('message')
+* content: the server answer that contains 3 elements: createAt, taskId and objectID
+*/
+Index.prototype.deleteObject = function(objectID, callback) {
+ if (typeof objectID === 'function' || typeof objectID !== 'string' && typeof objectID !== 'number') {
+ var err = new errors.AlgoliaSearchError(
+ objectID && typeof objectID !== 'function'
+ ? 'ObjectID must be a string'
+ : 'Cannot delete an object without an objectID'
+ );
+ callback = objectID;
+ if (typeof callback === 'function') {
+ return callback(err);
+ }
+
+ return this.as._promise.reject(err);
+ }
+
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'DELETE',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Delete several objects from an index
+*
+* @param objectIDs contains an array of objectID to delete
+* @param callback (optional) the result callback called with two arguments:
+* error: null or Error('message')
+* content: the server answer that contains 3 elements: createAt, taskId and objectID
+*/
+Index.prototype.deleteObjects = function(objectIDs, callback) {
+ var isArray = require('isarray');
+ var map = require('./map.js');
+
+ var usage = 'Usage: index.deleteObjects(arrayOfObjectIDs[, callback])';
+
+ if (!isArray(objectIDs)) {
+ throw new Error(usage);
+ }
+
+ var indexObj = this;
+ var postObj = {
+ requests: map(objectIDs, function prepareRequest(objectID) {
+ return {
+ action: 'deleteObject',
+ objectID: objectID,
+ body: {
+ objectID: objectID
+ }
+ };
+ })
+ };
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Delete all objects matching a query
+*
+* @param query the query string
+* @param params the optional query parameters
+* @param callback (optional) the result callback called with one argument
+* error: null or Error('message')
+* @deprecated see index.deleteBy
+*/
+Index.prototype.deleteByQuery = deprecate(function(query, params, callback) {
+ var clone = require('./clone.js');
+ var map = require('./map.js');
+
+ var indexObj = this;
+ var client = indexObj.as;
+
+ if (arguments.length === 1 || typeof params === 'function') {
+ callback = params;
+ params = {};
+ } else {
+ params = clone(params);
+ }
+
+ params.attributesToRetrieve = 'objectID';
+ params.hitsPerPage = 1000;
+ params.distinct = false;
+
+ // when deleting, we should never use cache to get the
+ // search results
+ this.clearCache();
+
+ // there's a problem in how we use the promise chain,
+ // see how waitTask is done
+ var promise = this
+ .search(query, params)
+ .then(stopOrDelete);
+
+ function stopOrDelete(searchContent) {
+ // stop here
+ if (searchContent.nbHits === 0) {
+ // return indexObj.as._request.resolve();
+ return searchContent;
+ }
+
+ // continue and do a recursive call
+ var objectIDs = map(searchContent.hits, function getObjectID(object) {
+ return object.objectID;
+ });
+
+ return indexObj
+ .deleteObjects(objectIDs)
+ .then(waitTask)
+ .then(doDeleteByQuery);
+ }
+
+ function waitTask(deleteObjectsContent) {
+ return indexObj.waitTask(deleteObjectsContent.taskID);
+ }
+
+ function doDeleteByQuery() {
+ return indexObj.deleteByQuery(query, params);
+ }
+
+ if (!callback) {
+ return promise;
+ }
+
+ promise.then(success, failure);
+
+ function success() {
+ exitPromise(function exit() {
+ callback(null);
+ }, client._setTimeout || setTimeout);
+ }
+
+ function failure(err) {
+ exitPromise(function exit() {
+ callback(err);
+ }, client._setTimeout || setTimeout);
+ }
+}, deprecatedMessage('index.deleteByQuery()', 'index.deleteBy()'));
+
+/**
+* Delete all objects matching a query
+*
+* the query parameters that can be used are:
+* - filters (numeric, facet, tag)
+* - geo
+*
+* you can not send an empty query or filters
+*
+* @param params the optional query parameters
+* @param callback (optional) the result callback called with one argument
+* error: null or Error('message')
+*/
+Index.prototype.deleteBy = function(params, callback) {
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/deleteByQuery',
+ body: {params: indexObj.as._getSearchParams(params, '')},
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Browse all content from an index using events. Basically this will do
+* .browse() -> .browseFrom -> .browseFrom -> .. until all the results are returned
+*
+* @param {string} query - The full text query
+* @param {Object} [queryParameters] - Any search query parameter
+* @return {EventEmitter}
+* @example
+* var browser = index.browseAll('cool songs', {
+* tagFilters: 'public,comments',
+* hitsPerPage: 500
+* });
+*
+* browser.on('result', function resultCallback(content) {
+* console.log(content.hits);
+* });
+*
+* // if any error occurs, you get it
+* browser.on('error', function(err) {
+* throw err;
+* });
+*
+* // when you have browsed the whole index, you get this event
+* browser.on('end', function() {
+* console.log('finished');
+* });
+*
+* // at any point if you want to stop the browsing process, you can stop it manually
+* // otherwise it will go on and on
+* browser.stop();
+*
+* @see {@link https://www.algolia.com/doc/rest_api#Browse|Algolia REST API Documentation}
+*/
+Index.prototype.browseAll = function(query, queryParameters) {
+ if (typeof query === 'object') {
+ queryParameters = query;
+ query = undefined;
+ }
+
+ var merge = require('./merge.js');
+
+ var IndexBrowser = require('./IndexBrowser');
+
+ var browser = new IndexBrowser();
+ var client = this.as;
+ var index = this;
+ var params = client._getSearchParams(
+ merge({}, queryParameters || {}, {
+ query: query
+ }), ''
+ );
+
+ // start browsing
+ browseLoop();
+
+ function browseLoop(cursor) {
+ if (browser._stopped) {
+ return;
+ }
+
+ var body;
+
+ if (cursor !== undefined) {
+ body = {
+ cursor: cursor
+ };
+ } else {
+ body = {
+ params: params
+ };
+ }
+
+ client._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(index.indexName) + '/browse',
+ hostType: 'read',
+ body: body,
+ callback: browseCallback
+ });
+ }
+
+ function browseCallback(err, content) {
+ if (browser._stopped) {
+ return;
+ }
+
+ if (err) {
+ browser._error(err);
+ return;
+ }
+
+ browser._result(content);
+
+ // no cursor means we are finished browsing
+ if (content.cursor === undefined) {
+ browser._end();
+ return;
+ }
+
+ browseLoop(content.cursor);
+ }
+
+ return browser;
+};
+
+/*
+* Get a Typeahead.js adapter
+* @param searchParams contains an object with query parameters (see search for details)
+*/
+Index.prototype.ttAdapter = deprecate(function(params) {
+ var self = this;
+ return function ttAdapter(query, syncCb, asyncCb) {
+ var cb;
+
+ if (typeof asyncCb === 'function') {
+ // typeahead 0.11
+ cb = asyncCb;
+ } else {
+ // pre typeahead 0.11
+ cb = syncCb;
+ }
+
+ self.search(query, params, function searchDone(err, content) {
+ if (err) {
+ cb(err);
+ return;
+ }
+
+ cb(content.hits);
+ });
+ };
+},
+'ttAdapter is not necessary anymore and will be removed in the next version,\n' +
+'have a look at autocomplete.js (https://github.com/algolia/autocomplete.js)');
+
+/*
+* Wait the publication of a task on the server.
+* All server task are asynchronous and you can check with this method that the task is published.
+*
+* @param taskID the id of the task returned by server
+* @param callback the result callback with with two arguments:
+* error: null or Error('message')
+* content: the server answer that contains the list of results
+*/
+Index.prototype.waitTask = function(taskID, callback) {
+ // wait minimum 100ms before retrying
+ var baseDelay = 100;
+ // wait maximum 5s before retrying
+ var maxDelay = 5000;
+ var loop = 0;
+
+ // waitTask() must be handled differently from other methods,
+ // it's a recursive method using a timeout
+ var indexObj = this;
+ var client = indexObj.as;
+
+ var promise = retryLoop();
+
+ function retryLoop() {
+ return client._jsonRequest({
+ method: 'GET',
+ hostType: 'read',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID
+ }).then(function success(content) {
+ loop++;
+ var delay = baseDelay * loop * loop;
+ if (delay > maxDelay) {
+ delay = maxDelay;
+ }
+
+ if (content.status !== 'published') {
+ return client._promise.delay(delay).then(retryLoop);
+ }
+
+ return content;
+ });
+ }
+
+ if (!callback) {
+ return promise;
+ }
+
+ promise.then(successCb, failureCb);
+
+ function successCb(content) {
+ exitPromise(function exit() {
+ callback(null, content);
+ }, client._setTimeout || setTimeout);
+ }
+
+ function failureCb(err) {
+ exitPromise(function exit() {
+ callback(err);
+ }, client._setTimeout || setTimeout);
+ }
+};
+
+/*
+* This function deletes the index content. Settings and index specific API keys are kept untouched.
+*
+* @param callback (optional) the result callback called with two arguments
+* error: null or Error('message')
+* content: the settings object or the error message if a failure occured
+*/
+Index.prototype.clearIndex = function(callback) {
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear',
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+/*
+* Get settings of this index
+*
+* @param opts an object of options to add
+* @param opts.advanced get more settings like nbShards (useful for Enterprise)
+* @param callback (optional) the result callback called with two arguments
+* error: null or Error('message')
+* content: the settings object or the error message if a failure occurred
+*/
+Index.prototype.getSettings = function(opts, callback) {
+ if (arguments.length === 1 && typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ }
+ opts = opts || {};
+
+ var indexName = encodeURIComponent(this.indexName);
+ return this.as._jsonRequest({
+ method: 'GET',
+ url:
+ '/1/indexes/' +
+ indexName +
+ '/settings?getVersion=2' +
+ (opts.advanced ? '&advanced=' + opts.advanced : ''),
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+Index.prototype.searchSynonyms = function(params, callback) {
+ if (typeof params === 'function') {
+ callback = params;
+ params = {};
+ } else if (params === undefined) {
+ params = {};
+ }
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/synonyms/search',
+ body: params,
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+function exportData(method, _hitsPerPage, callback) {
+ function search(page, _previous) {
+ var options = {
+ page: page || 0,
+ hitsPerPage: _hitsPerPage || 100
+ };
+ var previous = _previous || [];
+
+ return method(options).then(function(result) {
+ var hits = result.hits;
+ var nbHits = result.nbHits;
+ var current = hits.map(function(s) {
+ delete s._highlightResult;
+ return s;
+ });
+ var synonyms = previous.concat(current);
+ if (synonyms.length < nbHits) {
+ return search(options.page + 1, synonyms);
+ }
+ return synonyms;
+ });
+ }
+ return search().then(function(data) {
+ if (typeof callback === 'function') {
+ callback(data);
+ return undefined;
+ }
+ return data;
+ });
+}
+
+/**
+ * Retrieve all the synonyms in an index
+ * @param [number=100] hitsPerPage The amount of synonyms to retrieve per batch
+ * @param [function] callback will be called after all synonyms are retrieved
+ */
+Index.prototype.exportSynonyms = function(hitsPerPage, callback) {
+ return exportData(this.searchSynonyms.bind(this), hitsPerPage, callback);
+};
+
+Index.prototype.saveSynonym = function(synonym, opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ if (opts.forwardToSlaves !== undefined) deprecateForwardToSlaves();
+ var forwardToReplicas = (opts.forwardToSlaves || opts.forwardToReplicas) ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'PUT',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/synonyms/' + encodeURIComponent(synonym.objectID) +
+ '?forwardToReplicas=' + forwardToReplicas,
+ body: synonym,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+Index.prototype.getSynonym = function(objectID, callback) {
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/synonyms/' + encodeURIComponent(objectID),
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+Index.prototype.deleteSynonym = function(objectID, opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ if (opts.forwardToSlaves !== undefined) deprecateForwardToSlaves();
+ var forwardToReplicas = (opts.forwardToSlaves || opts.forwardToReplicas) ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'DELETE',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/synonyms/' + encodeURIComponent(objectID) +
+ '?forwardToReplicas=' + forwardToReplicas,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+Index.prototype.clearSynonyms = function(opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ if (opts.forwardToSlaves !== undefined) deprecateForwardToSlaves();
+ var forwardToReplicas = (opts.forwardToSlaves || opts.forwardToReplicas) ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/synonyms/clear' +
+ '?forwardToReplicas=' + forwardToReplicas,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+Index.prototype.batchSynonyms = function(synonyms, opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ if (opts.forwardToSlaves !== undefined) deprecateForwardToSlaves();
+ var forwardToReplicas = (opts.forwardToSlaves || opts.forwardToReplicas) ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/synonyms/batch' +
+ '?forwardToReplicas=' + forwardToReplicas +
+ '&replaceExistingSynonyms=' + (opts.replaceExistingSynonyms ? 'true' : 'false'),
+ hostType: 'write',
+ body: synonyms,
+ callback: callback
+ });
+};
+
+Index.prototype.searchRules = function(params, callback) {
+ if (typeof params === 'function') {
+ callback = params;
+ params = {};
+ } else if (params === undefined) {
+ params = {};
+ }
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/rules/search',
+ body: params,
+ hostType: 'read',
+ callback: callback
+ });
+};
+/**
+ * Retrieve all the query rules in an index
+ * @param [number=100] hitsPerPage The amount of query rules to retrieve per batch
+ * @param [function] callback will be called after all query rules are retrieved
+ * error: null or Error('message')
+ */
+Index.prototype.exportRules = function(hitsPerPage, callback) {
+ return exportData(this.searchRules.bind(this), hitsPerPage, callback);
+};
+
+Index.prototype.saveRule = function(rule, opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ if (!rule.objectID) {
+ throw new errors.AlgoliaSearchError('Missing or empty objectID field for rule');
+ }
+
+ var forwardToReplicas = opts.forwardToReplicas === true ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'PUT',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/rules/' + encodeURIComponent(rule.objectID) +
+ '?forwardToReplicas=' + forwardToReplicas,
+ body: rule,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+Index.prototype.getRule = function(objectID, callback) {
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/rules/' + encodeURIComponent(objectID),
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+Index.prototype.deleteRule = function(objectID, opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ var forwardToReplicas = opts.forwardToReplicas === true ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'DELETE',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/rules/' + encodeURIComponent(objectID) +
+ '?forwardToReplicas=' + forwardToReplicas,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+Index.prototype.clearRules = function(opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ var forwardToReplicas = opts.forwardToReplicas === true ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/rules/clear' +
+ '?forwardToReplicas=' + forwardToReplicas,
+ hostType: 'write',
+ callback: callback
+ });
+};
+
+Index.prototype.batchRules = function(rules, opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ } else if (opts === undefined) {
+ opts = {};
+ }
+
+ var forwardToReplicas = opts.forwardToReplicas === true ? 'true' : 'false';
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/rules/batch' +
+ '?forwardToReplicas=' + forwardToReplicas +
+ '&clearExistingRules=' + (opts.clearExistingRules === true ? 'true' : 'false'),
+ hostType: 'write',
+ body: rules,
+ callback: callback
+ });
+};
+
+/*
+* Set settings for this index
+*
+* @param settigns the settings object that can contains :
+* - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3).
+* - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7).
+* - hitsPerPage: (integer) the number of hits per page (default = 10).
+* - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
+* If set to null, all attributes are retrieved.
+* - attributesToHighlight: (array of strings) default list of attributes to highlight.
+* If set to null, all indexed attributes are highlighted.
+* - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number
+* of words to return (syntax is attributeName:nbWords).
+* By default no snippet is computed. If set to null, no snippet is computed.
+* - attributesToIndex: (array of strings) the list of fields you want to index.
+* If set to null, all textual and numerical attributes of your objects are indexed,
+* but you should update it to get optimal results.
+* This parameter has two important uses:
+* - Limit the attributes to index: For example if you store a binary image in base64,
+* you want to store it and be able to
+* retrieve it but you don't want to search in the base64 string.
+* - Control part of the ranking*: (see the ranking parameter for full explanation)
+* Matches in attributes at the beginning of
+* the list will be considered more important than matches in attributes further down the list.
+* In one attribute, matching text at the beginning of the attribute will be
+* considered more important than text after, you can disable
+* this behavior if you add your attribute inside `unordered(AttributeName)`,
+* for example attributesToIndex: ["title", "unordered(text)"].
+* - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
+* All strings in the attribute selected for faceting are extracted and added as a facet.
+* If set to null, no attribute is used for faceting.
+* - attributeForDistinct: (string) The attribute name used for the Distinct feature.
+* This feature is similar to the SQL "distinct" keyword: when enabled
+* in query with the distinct=1 parameter, all hits containing a duplicate
+* value for this attribute are removed from results.
+* For example, if the chosen attribute is show_name and several hits have
+* the same value for show_name, then only the best one is kept and others are removed.
+* - ranking: (array of strings) controls the way results are sorted.
+* We have six available criteria:
+* - typo: sort according to number of typos,
+* - geo: sort according to decreassing distance when performing a geo-location based search,
+* - proximity: sort according to the proximity of query words in hits,
+* - attribute: sort according to the order of attributes defined by attributesToIndex,
+* - exact:
+* - if the user query contains one word: sort objects having an attribute
+* that is exactly the query word before others.
+* For example if you search for the "V" TV show, you want to find it
+* with the "V" query and avoid to have all popular TV
+* show starting by the v letter before it.
+* - if the user query contains multiple words: sort according to the
+* number of words that matched exactly (and not as a prefix).
+* - custom: sort according to a user defined formula set in **customRanking** attribute.
+* The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
+* - customRanking: (array of strings) lets you specify part of the ranking.
+* The syntax of this condition is an array of strings containing attributes
+* prefixed by asc (ascending order) or desc (descending order) operator.
+* For example `"customRanking" => ["desc(population)", "asc(name)"]`
+* - queryType: Select how the query words are interpreted, it can be one of the following value:
+* - prefixAll: all query words are interpreted as prefixes,
+* - prefixLast: only the last word is interpreted as a prefix (default behavior),
+* - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
+* - highlightPreTag: (string) Specify the string that is inserted before
+* the highlighted parts in the query result (default to "<em>").
+* - highlightPostTag: (string) Specify the string that is inserted after
+* the highlighted parts in the query result (default to "</em>").
+* - optionalWords: (array of strings) Specify a list of words that should
+* be considered as optional when found in the query.
+* @param callback (optional) the result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer or the error message if a failure occured
+*/
+Index.prototype.setSettings = function(settings, opts, callback) {
+ if (arguments.length === 1 || typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ }
+
+ if (opts.forwardToSlaves !== undefined) deprecateForwardToSlaves();
+ var forwardToReplicas = (opts.forwardToSlaves || opts.forwardToReplicas) ? 'true' : 'false';
+
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'PUT',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings?forwardToReplicas='
+ + forwardToReplicas,
+ hostType: 'write',
+ body: settings,
+ callback: callback
+ });
+};
+
+/*
+* @deprecated see client.listApiKeys()
+*/
+Index.prototype.listUserKeys = deprecate(function(callback) {
+ return this.listApiKeys(callback);
+}, deprecatedMessage('index.listUserKeys()', 'client.listApiKeys()'));
+
+/*
+* List all existing API keys to this index
+*
+* @param callback the result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer with API keys belonging to the index
+*
+* @deprecated see client.listApiKeys()
+*/
+Index.prototype.listApiKeys = deprecate(function(callback) {
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
+ hostType: 'read',
+ callback: callback
+ });
+}, deprecatedMessage('index.listApiKeys()', 'client.listApiKeys()'));
+
+/*
+* @deprecated see client.getApiKey()
+*/
+Index.prototype.getUserKeyACL = deprecate(function(key, callback) {
+ return this.getApiKey(key, callback);
+}, deprecatedMessage('index.getUserKeyACL()', 'client.getApiKey()'));
+
+
+/*
+* Get an API key from this index
+*
+* @param key
+* @param callback the result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer with the right API key
+*
+* @deprecated see client.getApiKey()
+*/
+Index.prototype.getApiKey = deprecate(function(key, callback) {
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
+ hostType: 'read',
+ callback: callback
+ });
+}, deprecatedMessage('index.getApiKey()', 'client.getApiKey()'));
+
+/*
+* @deprecated see client.deleteApiKey()
+*/
+Index.prototype.deleteUserKey = deprecate(function(key, callback) {
+ return this.deleteApiKey(key, callback);
+}, deprecatedMessage('index.deleteUserKey()', 'client.deleteApiKey()'));
+
+/*
+* Delete an existing API key associated to this index
+*
+* @param key
+* @param callback the result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer with the deletion date
+*
+* @deprecated see client.deleteApiKey()
+*/
+Index.prototype.deleteApiKey = deprecate(function(key, callback) {
+ var indexObj = this;
+ return this.as._jsonRequest({
+ method: 'DELETE',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
+ hostType: 'write',
+ callback: callback
+ });
+}, deprecatedMessage('index.deleteApiKey()', 'client.deleteApiKey()'));
+
+/*
+* @deprecated see client.addApiKey()
+*/
+Index.prototype.addUserKey = deprecate(function(acls, params, callback) {
+ return this.addApiKey(acls, params, callback);
+}, deprecatedMessage('index.addUserKey()', 'client.addApiKey()'));
+
+/*
+* Add a new API key to this index
+*
+* @param {string[]} acls - The list of ACL for this key. Defined by an array of strings that
+* can contains the following values:
+* - search: allow to search (https and http)
+* - addObject: allows to add/update an object in the index (https only)
+* - deleteObject : allows to delete an existing object (https only)
+* - deleteIndex : allows to delete index content (https only)
+* - settings : allows to get index settings (https only)
+* - editSettings : allows to change index settings (https only)
+* @param {Object} [params] - Optionnal parameters to set for the key
+* @param {number} params.validity - Number of seconds after which the key will
+* be automatically removed (0 means no time limit for this key)
+* @param {number} params.maxQueriesPerIPPerHour - Number of API calls allowed from an IP address per hour
+* @param {number} params.maxHitsPerQuery - Number of hits this API key can retrieve in one call
+* @param {string} params.description - A description for your key
+* @param {string[]} params.referers - A list of authorized referers
+* @param {Object} params.queryParameters - Force the key to use specific query parameters
+* @param {Function} callback - The result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer with the added API key
+* @return {Promise|undefined} Returns a promise if no callback given
+* @example
+* index.addUserKey(['search'], {
+* validity: 300,
+* maxQueriesPerIPPerHour: 2000,
+* maxHitsPerQuery: 3,
+* description: 'Eat three fruits',
+* referers: ['*.algolia.com'],
+* queryParameters: {
+* tagFilters: ['public'],
+* }
+* })
+* @see {@link https://www.algolia.com/doc/rest_api#AddIndexKey|Algolia REST API Documentation}
+*
+* @deprecated see client.addApiKey()
+*/
+Index.prototype.addApiKey = deprecate(function(acls, params, callback) {
+ var isArray = require('isarray');
+ var usage = 'Usage: index.addApiKey(arrayOfAcls[, params, callback])';
+
+ if (!isArray(acls)) {
+ throw new Error(usage);
+ }
+
+ if (arguments.length === 1 || typeof params === 'function') {
+ callback = params;
+ params = null;
+ }
+
+ var postObj = {
+ acl: acls
+ };
+
+ if (params) {
+ postObj.validity = params.validity;
+ postObj.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour;
+ postObj.maxHitsPerQuery = params.maxHitsPerQuery;
+ postObj.description = params.description;
+
+ if (params.queryParameters) {
+ postObj.queryParameters = this.as._getSearchParams(params.queryParameters, '');
+ }
+
+ postObj.referers = params.referers;
+ }
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/keys',
+ body: postObj,
+ hostType: 'write',
+ callback: callback
+ });
+}, deprecatedMessage('index.addApiKey()', 'client.addApiKey()'));
+
+/**
+* @deprecated use client.addApiKey()
+*/
+Index.prototype.addUserKeyWithValidity = deprecate(function deprecatedAddUserKeyWithValidity(acls, params, callback) {
+ return this.addApiKey(acls, params, callback);
+}, deprecatedMessage('index.addUserKeyWithValidity()', 'client.addApiKey()'));
+
+/*
+* @deprecated see client.updateApiKey()
+*/
+Index.prototype.updateUserKey = deprecate(function(key, acls, params, callback) {
+ return this.updateApiKey(key, acls, params, callback);
+}, deprecatedMessage('index.updateUserKey()', 'client.updateApiKey()'));
+
+/**
+* Update an existing API key of this index
+* @param {string} key - The key to update
+* @param {string[]} acls - The list of ACL for this key. Defined by an array of strings that
+* can contains the following values:
+* - search: allow to search (https and http)
+* - addObject: allows to add/update an object in the index (https only)
+* - deleteObject : allows to delete an existing object (https only)
+* - deleteIndex : allows to delete index content (https only)
+* - settings : allows to get index settings (https only)
+* - editSettings : allows to change index settings (https only)
+* @param {Object} [params] - Optionnal parameters to set for the key
+* @param {number} params.validity - Number of seconds after which the key will
+* be automatically removed (0 means no time limit for this key)
+* @param {number} params.maxQueriesPerIPPerHour - Number of API calls allowed from an IP address per hour
+* @param {number} params.maxHitsPerQuery - Number of hits this API key can retrieve in one call
+* @param {string} params.description - A description for your key
+* @param {string[]} params.referers - A list of authorized referers
+* @param {Object} params.queryParameters - Force the key to use specific query parameters
+* @param {Function} callback - The result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer with user keys list
+* @return {Promise|undefined} Returns a promise if no callback given
+* @example
+* index.updateApiKey('APIKEY', ['search'], {
+* validity: 300,
+* maxQueriesPerIPPerHour: 2000,
+* maxHitsPerQuery: 3,
+* description: 'Eat three fruits',
+* referers: ['*.algolia.com'],
+* queryParameters: {
+* tagFilters: ['public'],
+* }
+* })
+* @see {@link https://www.algolia.com/doc/rest_api#UpdateIndexKey|Algolia REST API Documentation}
+*
+* @deprecated see client.updateApiKey()
+*/
+Index.prototype.updateApiKey = deprecate(function(key, acls, params, callback) {
+ var isArray = require('isarray');
+ var usage = 'Usage: index.updateApiKey(key, arrayOfAcls[, params, callback])';
+
+ if (!isArray(acls)) {
+ throw new Error(usage);
+ }
+
+ if (arguments.length === 2 || typeof params === 'function') {
+ callback = params;
+ params = null;
+ }
+
+ var putObj = {
+ acl: acls
+ };
+
+ if (params) {
+ putObj.validity = params.validity;
+ putObj.maxQueriesPerIPPerHour = params.maxQueriesPerIPPerHour;
+ putObj.maxHitsPerQuery = params.maxHitsPerQuery;
+ putObj.description = params.description;
+
+ if (params.queryParameters) {
+ putObj.queryParameters = this.as._getSearchParams(params.queryParameters, '');
+ }
+
+ putObj.referers = params.referers;
+ }
+
+ return this.as._jsonRequest({
+ method: 'PUT',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/keys/' + key,
+ body: putObj,
+ hostType: 'write',
+ callback: callback
+ });
+}, deprecatedMessage('index.updateApiKey()', 'client.updateApiKey()'));
diff --git a/node_modules/algoliasearch/src/IndexBrowser.js b/node_modules/algoliasearch/src/IndexBrowser.js
new file mode 100644
index 00000000..b035a200
--- /dev/null
+++ b/node_modules/algoliasearch/src/IndexBrowser.js
@@ -0,0 +1,39 @@
+'use strict';
+
+// This is the object returned by the `index.browseAll()` method
+
+module.exports = IndexBrowser;
+
+var inherits = require('inherits');
+var EventEmitter = require('events').EventEmitter;
+
+function IndexBrowser() {
+}
+
+inherits(IndexBrowser, EventEmitter);
+
+IndexBrowser.prototype.stop = function() {
+ this._stopped = true;
+ this._clean();
+};
+
+IndexBrowser.prototype._end = function() {
+ this.emit('end');
+ this._clean();
+};
+
+IndexBrowser.prototype._error = function(err) {
+ this.emit('error', err);
+ this._clean();
+};
+
+IndexBrowser.prototype._result = function(content) {
+ this.emit('result', content);
+};
+
+IndexBrowser.prototype._clean = function() {
+ this.removeAllListeners('stop');
+ this.removeAllListeners('end');
+ this.removeAllListeners('error');
+ this.removeAllListeners('result');
+};
diff --git a/node_modules/algoliasearch/src/IndexCore.js b/node_modules/algoliasearch/src/IndexCore.js
new file mode 100644
index 00000000..f06b783b
--- /dev/null
+++ b/node_modules/algoliasearch/src/IndexCore.js
@@ -0,0 +1,383 @@
+var buildSearchMethod = require('./buildSearchMethod.js');
+var deprecate = require('./deprecate.js');
+var deprecatedMessage = require('./deprecatedMessage.js');
+
+module.exports = IndexCore;
+
+/*
+* Index class constructor.
+* You should not use this method directly but use initIndex() function
+*/
+function IndexCore(algoliasearch, indexName) {
+ this.indexName = indexName;
+ this.as = algoliasearch;
+ this.typeAheadArgs = null;
+ this.typeAheadValueOption = null;
+
+ // make sure every index instance has it's own cache
+ this.cache = {};
+}
+
+/*
+* Clear all queries in cache
+*/
+IndexCore.prototype.clearCache = function() {
+ this.cache = {};
+};
+
+/*
+* Search inside the index using XMLHttpRequest request (Using a POST query to
+* minimize number of OPTIONS queries: Cross-Origin Resource Sharing).
+*
+* @param {string} [query] the full text query
+* @param {object} [args] (optional) if set, contains an object with query parameters:
+* - page: (integer) Pagination parameter used to select the page to retrieve.
+* Page is zero-based and defaults to 0. Thus,
+* to retrieve the 10th page you need to set page=9
+* - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20.
+* - attributesToRetrieve: a string that contains the list of object attributes
+* you want to retrieve (let you minimize the answer size).
+* Attributes are separated with a comma (for example "name,address").
+* You can also use an array (for example ["name","address"]).
+* By default, all attributes are retrieved. You can also use '*' to retrieve all
+* values when an attributesToRetrieve setting is specified for your index.
+* - attributesToHighlight: a string that contains the list of attributes you
+* want to highlight according to the query.
+* Attributes are separated by a comma. You can also use an array (for example ["name","address"]).
+* If an attribute has no match for the query, the raw value is returned.
+* By default all indexed text attributes are highlighted.
+* You can use `*` if you want to highlight all textual attributes.
+* Numerical attributes are not highlighted.
+* A matchLevel is returned for each highlighted attribute and can contain:
+* - full: if all the query terms were found in the attribute,
+* - partial: if only some of the query terms were found,
+* - none: if none of the query terms were found.
+* - attributesToSnippet: a string that contains the list of attributes to snippet alongside
+* the number of words to return (syntax is `attributeName:nbWords`).
+* Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10).
+* You can also use an array (Example: attributesToSnippet: ['name:10','content:10']).
+* By default no snippet is computed.
+* - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word.
+* Defaults to 3.
+* - minWordSizefor2Typos: the minimum number of characters in a query word
+* to accept two typos in this word. Defaults to 7.
+* - getRankingInfo: if set to 1, the result hits will contain ranking
+* information in _rankingInfo attribute.
+* - aroundLatLng: search for entries around a given
+* latitude/longitude (specified as two floats separated by a comma).
+* For example aroundLatLng=47.316669,5.016670).
+* You can specify the maximum distance in meters with the aroundRadius parameter (in meters)
+* and the precision for ranking with aroundPrecision
+* (for example if you set aroundPrecision=100, two objects that are distant of
+* less than 100m will be considered as identical for "geo" ranking parameter).
+* At indexing, you should specify geoloc of an object with the _geoloc attribute
+* (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
+* - insideBoundingBox: search entries inside a given area defined by the two extreme points
+* of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng).
+* For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201).
+* At indexing, you should specify geoloc of an object with the _geoloc attribute
+* (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
+* - numericFilters: a string that contains the list of numeric filters you want to
+* apply separated by a comma.
+* The syntax of one filter is `attributeName` followed by `operand` followed by `value`.
+* Supported operands are `<`, `<=`, `=`, `>` and `>=`.
+* You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000.
+* You can also use an array (for example numericFilters: ["price>100","price<1000"]).
+* - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas.
+* To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3).
+* You can also use an array, for example tagFilters: ["tag1",["tag2","tag3"]]
+* means tag1 AND (tag2 OR tag3).
+* At indexing, tags should be added in the _tags** attribute
+* of objects (for example {"_tags":["tag1","tag2"]}).
+* - facetFilters: filter the query by a list of facets.
+* Facets are separated by commas and each facet is encoded as `attributeName:value`.
+* For example: `facetFilters=category:Book,author:John%20Doe`.
+* You can also use an array (for example `["category:Book","author:John%20Doe"]`).
+* - facets: List of object attributes that you want to use for faceting.
+* Comma separated list: `"category,author"` or array `['category','author']`
+* Only attributes that have been added in **attributesForFaceting** index setting
+* can be used in this parameter.
+* You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**.
+* - queryType: select how the query words are interpreted, it can be one of the following value:
+* - prefixAll: all query words are interpreted as prefixes,
+* - prefixLast: only the last word is interpreted as a prefix (default behavior),
+* - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
+* - optionalWords: a string that contains the list of words that should
+* be considered as optional when found in the query.
+* Comma separated and array are accepted.
+* - distinct: If set to 1, enable the distinct feature (disabled by default)
+* if the attributeForDistinct index setting is set.
+* This feature is similar to the SQL "distinct" keyword: when enabled
+* in a query with the distinct=1 parameter,
+* all hits containing a duplicate value for the attributeForDistinct attribute are removed from results.
+* For example, if the chosen attribute is show_name and several hits have
+* the same value for show_name, then only the best
+* one is kept and others are removed.
+* - restrictSearchableAttributes: List of attributes you want to use for
+* textual search (must be a subset of the attributesToIndex index setting)
+* either comma separated or as an array
+* @param {function} [callback] the result callback called 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.
+*/
+IndexCore.prototype.search = buildSearchMethod('query');
+
+/*
+* -- BETA --
+* Search a record similar to the query inside the index using XMLHttpRequest request (Using a POST query to
+* minimize number of OPTIONS queries: Cross-Origin Resource Sharing).
+*
+* @param {string} [query] the similar query
+* @param {object} [args] (optional) if set, contains an object with query parameters.
+* All search parameters are supported (see search function), restrictSearchableAttributes and facetFilters
+* are the two most useful to restrict the similar results and get more relevant content
+*/
+IndexCore.prototype.similarSearch = buildSearchMethod('similarQuery');
+
+/*
+* Browse index content. The response content will have a `cursor` property that you can use
+* to browse subsequent pages for this query. Use `index.browseFrom(cursor)` when you want.
+*
+* @param {string} query - The full text query
+* @param {Object} [queryParameters] - Any search query parameter
+* @param {Function} [callback] - The result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer with the browse result
+* @return {Promise|undefined} Returns a promise if no callback given
+* @example
+* index.browse('cool songs', {
+* tagFilters: 'public,comments',
+* hitsPerPage: 500
+* }, callback);
+* @see {@link https://www.algolia.com/doc/rest_api#Browse|Algolia REST API Documentation}
+*/
+IndexCore.prototype.browse = function(query, queryParameters, callback) {
+ var merge = require('./merge.js');
+
+ var indexObj = this;
+
+ var page;
+ var hitsPerPage;
+
+ // we check variadic calls that are not the one defined
+ // .browse()/.browse(fn)
+ // => page = 0
+ if (arguments.length === 0 || arguments.length === 1 && typeof arguments[0] === 'function') {
+ page = 0;
+ callback = arguments[0];
+ query = undefined;
+ } else if (typeof arguments[0] === 'number') {
+ // .browse(2)/.browse(2, 10)/.browse(2, fn)/.browse(2, 10, fn)
+ page = arguments[0];
+ if (typeof arguments[1] === 'number') {
+ hitsPerPage = arguments[1];
+ } else if (typeof arguments[1] === 'function') {
+ callback = arguments[1];
+ hitsPerPage = undefined;
+ }
+ query = undefined;
+ queryParameters = undefined;
+ } else if (typeof arguments[0] === 'object') {
+ // .browse(queryParameters)/.browse(queryParameters, cb)
+ if (typeof arguments[1] === 'function') {
+ callback = arguments[1];
+ }
+ queryParameters = arguments[0];
+ query = undefined;
+ } else if (typeof arguments[0] === 'string' && typeof arguments[1] === 'function') {
+ // .browse(query, cb)
+ callback = arguments[1];
+ queryParameters = undefined;
+ }
+
+ // otherwise it's a .browse(query)/.browse(query, queryParameters)/.browse(query, queryParameters, cb)
+
+ // get search query parameters combining various possible calls
+ // to .browse();
+ queryParameters = merge({}, queryParameters || {}, {
+ page: page,
+ hitsPerPage: hitsPerPage,
+ query: query
+ });
+
+ var params = this.as._getSearchParams(queryParameters, '');
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse',
+ body: {params: params},
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/*
+* Continue browsing from a previous position (cursor), obtained via a call to `.browse()`.
+*
+* @param {string} query - The full text query
+* @param {Object} [queryParameters] - Any search query parameter
+* @param {Function} [callback] - The result callback called with two arguments
+* error: null or Error('message')
+* content: the server answer with the browse result
+* @return {Promise|undefined} Returns a promise if no callback given
+* @example
+* index.browseFrom('14lkfsakl32', callback);
+* @see {@link https://www.algolia.com/doc/rest_api#Browse|Algolia REST API Documentation}
+*/
+IndexCore.prototype.browseFrom = function(cursor, callback) {
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/browse',
+ body: {cursor: cursor},
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/*
+* Search for facet values
+* https://www.algolia.com/doc/rest-api/search#search-for-facet-values
+*
+* @param {string} params.facetName Facet name, name of the attribute to search for values in.
+* Must be declared as a facet
+* @param {string} params.facetQuery Query for the facet search
+* @param {string} [params.*] Any search parameter of Algolia,
+* see https://www.algolia.com/doc/api-client/javascript/search#search-parameters
+* Pagination is not supported. The page and hitsPerPage parameters will be ignored.
+* @param callback (optional)
+*/
+IndexCore.prototype.searchForFacetValues = function(params, callback) {
+ var clone = require('./clone.js');
+ var omit = require('./omit.js');
+ var usage = 'Usage: index.searchForFacetValues({facetName, facetQuery, ...params}[, callback])';
+
+ if (params.facetName === undefined || params.facetQuery === undefined) {
+ throw new Error(usage);
+ }
+
+ var facetName = params.facetName;
+ var filteredParams = omit(clone(params), function(keyName) {
+ return keyName === 'facetName';
+ });
+ var searchParameters = this.as._getSearchParams(filteredParams, '');
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/' +
+ encodeURIComponent(this.indexName) + '/facets/' + encodeURIComponent(facetName) + '/query',
+ hostType: 'read',
+ body: {params: searchParameters},
+ callback: callback
+ });
+};
+
+IndexCore.prototype.searchFacet = deprecate(function(params, callback) {
+ return this.searchForFacetValues(params, callback);
+}, deprecatedMessage(
+ 'index.searchFacet(params[, callback])',
+ 'index.searchForFacetValues(params[, callback])'
+));
+
+IndexCore.prototype._search = function(params, url, callback, additionalUA) {
+ return this.as._jsonRequest({
+ cache: this.cache,
+ method: 'POST',
+ url: url || '/1/indexes/' + encodeURIComponent(this.indexName) + '/query',
+ body: {params: params},
+ hostType: 'read',
+ fallback: {
+ method: 'GET',
+ url: '/1/indexes/' + encodeURIComponent(this.indexName),
+ body: {params: params}
+ },
+ callback: callback,
+ additionalUA: additionalUA
+ });
+};
+
+/*
+* Get an object from this index
+*
+* @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 called with two arguments
+* error: null or Error('message')
+* content: the object to retrieve or the error message if a failure occured
+*/
+IndexCore.prototype.getObject = function(objectID, attrs, callback) {
+ var indexObj = this;
+
+ if (arguments.length === 1 || typeof attrs === 'function') {
+ callback = attrs;
+ attrs = undefined;
+ }
+
+ var params = '';
+ if (attrs !== undefined) {
+ params = '?attributes=';
+ for (var i = 0; i < attrs.length; ++i) {
+ if (i !== 0) {
+ params += ',';
+ }
+ params += attrs[i];
+ }
+ }
+
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params,
+ hostType: 'read',
+ callback: callback
+ });
+};
+
+/*
+* Get several objects from this index
+*
+* @param objectIDs the array of unique identifier of objects to retrieve
+*/
+IndexCore.prototype.getObjects = function(objectIDs, attributesToRetrieve, callback) {
+ var isArray = require('isarray');
+ var map = require('./map.js');
+
+ var usage = 'Usage: index.getObjects(arrayOfObjectIDs[, callback])';
+
+ if (!isArray(objectIDs)) {
+ throw new Error(usage);
+ }
+
+ var indexObj = this;
+
+ if (arguments.length === 1 || typeof attributesToRetrieve === 'function') {
+ callback = attributesToRetrieve;
+ attributesToRetrieve = undefined;
+ }
+
+ var body = {
+ requests: map(objectIDs, function prepareRequest(objectID) {
+ var request = {
+ indexName: indexObj.indexName,
+ objectID: objectID
+ };
+
+ if (attributesToRetrieve) {
+ request.attributesToRetrieve = attributesToRetrieve.join(',');
+ }
+
+ return request;
+ })
+ };
+
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/1/indexes/*/objects',
+ hostType: 'read',
+ body: body,
+ callback: callback
+ });
+};
+
+IndexCore.prototype.as = null;
+IndexCore.prototype.indexName = null;
+IndexCore.prototype.typeAheadArgs = null;
+IndexCore.prototype.typeAheadValueOption = null;
diff --git a/node_modules/algoliasearch/src/browser/builds/algoliasearch.angular.js b/node_modules/algoliasearch/src/browser/builds/algoliasearch.angular.js
new file mode 100644
index 00000000..c1db3343
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/builds/algoliasearch.angular.js
@@ -0,0 +1,201 @@
+'use strict';
+
+// This is the AngularJS Algolia Search module
+// It's using $http to do requests with a JSONP fallback
+// $q promises are returned
+
+var inherits = require('inherits');
+
+var forEach = require('foreach');
+
+var AlgoliaSearch = require('../../AlgoliaSearch');
+var errors = require('../../errors');
+var inlineHeaders = require('../inline-headers');
+var jsonpRequest = require('../jsonp-request');
+var places = require('../../places.js');
+
+// expose original algoliasearch fn in window
+window.algoliasearch = require('./algoliasearch');
+
+if (process.env.NODE_ENV === 'debug') {
+ require('debug').enable('algoliasearch*');
+}
+
+window.angular.module('algoliasearch', [])
+ .service('algolia', ['$http', '$q', '$timeout', function algoliaSearchService($http, $q, $timeout) {
+ function algoliasearch(applicationID, apiKey, opts) {
+ var cloneDeep = require('../../clone.js');
+
+ opts = cloneDeep(opts || {});
+
+ opts._ua = opts._ua || algoliasearch.ua;
+
+ return new AlgoliaSearchAngular(applicationID, apiKey, opts);
+ }
+
+ algoliasearch.version = require('../../version.js');
+ algoliasearch.ua = 'Algolia for AngularJS ' + algoliasearch.version;
+ algoliasearch.initPlaces = places(algoliasearch);
+
+ // we expose into window no matter how we are used, this will allow
+ // us to easily debug any website running algolia
+ window.__algolia = {
+ debug: require('debug'),
+ algoliasearch: algoliasearch
+ };
+
+ function AlgoliaSearchAngular() {
+ // call AlgoliaSearch constructor
+ AlgoliaSearch.apply(this, arguments);
+ }
+
+ inherits(AlgoliaSearchAngular, AlgoliaSearch);
+
+ AlgoliaSearchAngular.prototype._request = function request(url, opts) {
+ // Support most Angular.js versions by using $q.defer() instead
+ // of the new $q() constructor everywhere we need a promise
+ var deferred = $q.defer();
+ var resolve = deferred.resolve;
+ var reject = deferred.reject;
+
+ var timedOut;
+ var body = opts.body;
+
+ url = inlineHeaders(url, opts.headers);
+
+ var timeoutDeferred = $q.defer();
+ var timeoutPromise = timeoutDeferred.promise;
+
+ $timeout(function timedout() {
+ timedOut = true;
+ // will cancel the xhr
+ timeoutDeferred.resolve('test');
+ reject(new errors.RequestTimeout());
+ }, opts.timeouts.complete);
+
+ var requestHeaders = {};
+
+ // "remove" (set to undefined) possible globally set headers
+ // in $httpProvider.defaults.headers.common
+ // otherwise we might fail sometimes
+ // ref: https://github.com/algolia/algoliasearch-client-js/issues/135
+ forEach(
+ $http.defaults.headers.common,
+ function removeIt(headerValue, headerName) {
+ requestHeaders[headerName] = undefined;
+ }
+ );
+
+ requestHeaders.accept = 'application/json';
+
+ if (body) {
+ if (opts.method === 'POST') {
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Simple_requests
+ requestHeaders['content-type'] = 'application/x-www-form-urlencoded';
+ } else {
+ requestHeaders['content-type'] = 'application/json';
+ }
+ }
+
+ $http({
+ url: url,
+ method: opts.method,
+ data: body,
+ cache: false,
+ timeout: timeoutPromise,
+ headers: requestHeaders,
+ transformResponse: transformResponse,
+ // if client uses $httpProvider.defaults.withCredentials = true,
+ // we revert it to false to avoid CORS failure
+ withCredentials: false
+ }).then(success, error);
+
+ function success(response) {
+ resolve({
+ statusCode: response.status,
+ headers: response.headers,
+ body: JSON.parse(response.data),
+ responseText: response.data
+ });
+ }
+
+ // we force getting the raw data because we need it so
+ // for cache keys
+ function transformResponse(data) {
+ return data;
+ }
+
+ function error(response) {
+ if (timedOut) {
+ return;
+ }
+
+ // network error
+ if (response.status === 0) {
+ reject(
+ new errors.Network({
+ more: response
+ })
+ );
+ return;
+ }
+
+ resolve({
+ body: JSON.parse(response.data),
+ statusCode: response.status
+ });
+ }
+
+ return deferred.promise;
+ };
+
+ // using IE8 or IE9 we will always end up here
+ // AngularJS does not fallback to XDomainRequest
+ AlgoliaSearchAngular.prototype._request.fallback = function requestFallback(url, opts) {
+ url = inlineHeaders(url, opts.headers);
+
+ var deferred = $q.defer();
+ var resolve = deferred.resolve;
+ var reject = deferred.reject;
+
+ jsonpRequest(url, opts, function jsonpRequestDone(err, content) {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ resolve(content);
+ });
+
+ return deferred.promise;
+ };
+
+ AlgoliaSearchAngular.prototype._promise = {
+ reject: function(val) {
+ return $q.reject(val);
+ },
+ resolve: function(val) {
+ // http://www.bennadel.com/blog/2735-q-when-is-the-missing-q-resolve-method-in-angularjs.htm
+ return $q.when(val);
+ },
+ delay: function(ms) {
+ var deferred = $q.defer();
+ var resolve = deferred.resolve;
+
+ $timeout(resolve, ms);
+
+ return deferred.promise;
+ },
+ all: function(promises) {
+ return $q.all(promises);
+ }
+ };
+
+ return {
+ Client: function(applicationID, apiKey, options) {
+ return algoliasearch(applicationID, apiKey, options);
+ },
+ ua: algoliasearch.ua,
+ version: algoliasearch.version
+ };
+ }]);
diff --git a/node_modules/algoliasearch/src/browser/builds/algoliasearch.jquery.js b/node_modules/algoliasearch/src/browser/builds/algoliasearch.jquery.js
new file mode 100644
index 00000000..3b8bebb9
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/builds/algoliasearch.jquery.js
@@ -0,0 +1,147 @@
+'use strict';
+
+// This is the jQuery Algolia Search module
+// It's using $.ajax to do requests with a JSONP fallback
+// jQuery promises are returned
+
+var inherits = require('inherits');
+
+var AlgoliaSearch = require('../../AlgoliaSearch');
+var errors = require('../../errors');
+var inlineHeaders = require('../inline-headers');
+var jsonpRequest = require('../jsonp-request');
+var places = require('../../places.js');
+
+// expose original algoliasearch fn in window
+window.algoliasearch = require('./algoliasearch');
+
+if (process.env.NODE_ENV === 'debug') {
+ require('debug').enable('algoliasearch*');
+}
+
+function algoliasearch(applicationID, apiKey, opts) {
+ var cloneDeep = require('../../clone.js');
+
+ opts = cloneDeep(opts || {});
+
+ opts._ua = opts._ua || algoliasearch.ua;
+
+ return new AlgoliaSearchJQuery(applicationID, apiKey, opts);
+}
+
+algoliasearch.version = require('../../version.js');
+algoliasearch.ua = 'Algolia for jQuery ' + algoliasearch.version;
+algoliasearch.initPlaces = places(algoliasearch);
+
+// we expose into window no matter how we are used, this will allow
+// us to easily debug any website running algolia
+window.__algolia = {
+ debug: require('debug'),
+ algoliasearch: algoliasearch
+};
+
+var $ = window.jQuery;
+
+$.algolia = {
+ Client: algoliasearch,
+ ua: algoliasearch.ua,
+ version: algoliasearch.version
+};
+
+function AlgoliaSearchJQuery() {
+ // call AlgoliaSearch constructor
+ AlgoliaSearch.apply(this, arguments);
+}
+
+inherits(AlgoliaSearchJQuery, AlgoliaSearch);
+
+AlgoliaSearchJQuery.prototype._request = function request(url, opts) {
+ return new $.Deferred(function(deferred) {
+ var body = opts.body;
+
+ url = inlineHeaders(url, opts.headers);
+
+ var requestHeaders = {
+ accept: 'application/json'
+ };
+
+ if (body) {
+ if (opts.method === 'POST') {
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Simple_requests
+ requestHeaders['content-type'] = 'application/x-www-form-urlencoded';
+ } else {
+ requestHeaders['content-type'] = 'application/json';
+ }
+ }
+
+ $.ajax(url, {
+ type: opts.method,
+ timeout: opts.timeouts.complete,
+ dataType: 'json',
+ data: body,
+ headers: requestHeaders,
+ complete: function onComplete(jqXHR, textStatus/* , error*/) {
+ if (textStatus === 'timeout') {
+ deferred.reject(new errors.RequestTimeout());
+ return;
+ }
+
+ if (jqXHR.status === 0) {
+ deferred.reject(
+ new errors.Network({
+ more: jqXHR
+ })
+ );
+ return;
+ }
+
+ deferred.resolve({
+ statusCode: jqXHR.status,
+ body: jqXHR.responseJSON,
+ responseText: jqXHR.responseText,
+ headers: jqXHR.getAllResponseHeaders()
+ });
+ }
+ });
+ }).promise();
+};
+
+// using IE8 or IE9 we will always end up here
+// jQuery does not not fallback to XDomainRequest
+AlgoliaSearchJQuery.prototype._request.fallback = function requestFallback(url, opts) {
+ url = inlineHeaders(url, opts.headers);
+
+ return new $.Deferred(function wrapJsonpRequest(deferred) {
+ jsonpRequest(url, opts, function jsonpRequestDone(err, content) {
+ if (err) {
+ deferred.reject(err);
+ return;
+ }
+
+ deferred.resolve(content);
+ });
+ }).promise();
+};
+
+AlgoliaSearchJQuery.prototype._promise = {
+ reject: function reject(val) {
+ return new $.Deferred(function rejectDeferred(deferred) {
+ deferred.reject(val);
+ }).promise();
+ },
+ resolve: function resolve(val) {
+ return new $.Deferred(function resolveDeferred(deferred) {
+ deferred.resolve(val);
+ }).promise();
+ },
+ delay: function delay(ms) {
+ return new $.Deferred(function delayResolve(deferred) {
+ setTimeout(function resolveDeferred() {
+ deferred.resolve();
+ }, ms);
+ }).promise();
+ },
+ all: function all(promises) {
+ return $.when.apply(null, promises);
+ }
+};
diff --git a/node_modules/algoliasearch/src/browser/builds/algoliasearch.js b/node_modules/algoliasearch/src/browser/builds/algoliasearch.js
new file mode 100644
index 00000000..e8b820db
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/builds/algoliasearch.js
@@ -0,0 +1,6 @@
+'use strict';
+
+var AlgoliaSearch = require('../../AlgoliaSearch.js');
+var createAlgoliasearch = require('../createAlgoliasearch.js');
+
+module.exports = createAlgoliasearch(AlgoliaSearch);
diff --git a/node_modules/algoliasearch/src/browser/builds/algoliasearchLite.js b/node_modules/algoliasearch/src/browser/builds/algoliasearchLite.js
new file mode 100644
index 00000000..ca0b4b13
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/builds/algoliasearchLite.js
@@ -0,0 +1,6 @@
+'use strict';
+
+var AlgoliaSearchCore = require('../../AlgoliaSearchCore.js');
+var createAlgoliasearch = require('../createAlgoliasearch.js');
+
+module.exports = createAlgoliasearch(AlgoliaSearchCore, '(lite) ');
diff --git a/node_modules/algoliasearch/src/browser/createAlgoliasearch.js b/node_modules/algoliasearch/src/browser/createAlgoliasearch.js
new file mode 100644
index 00000000..a2a488b8
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/createAlgoliasearch.js
@@ -0,0 +1,233 @@
+'use strict';
+
+var global = require('global');
+var Promise = global.Promise || require('es6-promise').Promise;
+
+// This is the standalone browser build entry point
+// Browser implementation of the Algolia Search JavaScript client,
+// using XMLHttpRequest, XDomainRequest and JSONP as fallback
+module.exports = function createAlgoliasearch(AlgoliaSearch, uaSuffix) {
+ var inherits = require('inherits');
+ var errors = require('../errors');
+ var inlineHeaders = require('./inline-headers');
+ var jsonpRequest = require('./jsonp-request');
+ var places = require('../places.js');
+ uaSuffix = uaSuffix || '';
+
+ if (process.env.NODE_ENV === 'debug') {
+ require('debug').enable('algoliasearch*');
+ }
+
+ function algoliasearch(applicationID, apiKey, opts) {
+ var cloneDeep = require('../clone.js');
+
+ opts = cloneDeep(opts || {});
+
+ opts._ua = opts._ua || algoliasearch.ua;
+
+ return new AlgoliaSearchBrowser(applicationID, apiKey, opts);
+ }
+
+ algoliasearch.version = require('../version.js');
+ algoliasearch.ua = 'Algolia for vanilla JavaScript ' + uaSuffix + algoliasearch.version;
+ algoliasearch.initPlaces = places(algoliasearch);
+
+ // we expose into window no matter how we are used, this will allow
+ // us to easily debug any website running algolia
+ global.__algolia = {
+ debug: require('debug'),
+ algoliasearch: algoliasearch
+ };
+
+ var support = {
+ hasXMLHttpRequest: 'XMLHttpRequest' in global,
+ hasXDomainRequest: 'XDomainRequest' in global
+ };
+
+ if (support.hasXMLHttpRequest) {
+ support.cors = 'withCredentials' in new XMLHttpRequest();
+ }
+
+ function AlgoliaSearchBrowser() {
+ // call AlgoliaSearch constructor
+ AlgoliaSearch.apply(this, arguments);
+ }
+
+ inherits(AlgoliaSearchBrowser, AlgoliaSearch);
+
+ AlgoliaSearchBrowser.prototype._request = function request(url, opts) {
+ return new Promise(function wrapRequest(resolve, reject) {
+ // no cors or XDomainRequest, no request
+ if (!support.cors && !support.hasXDomainRequest) {
+ // very old browser, not supported
+ reject(new errors.Network('CORS not supported'));
+ return;
+ }
+
+ url = inlineHeaders(url, opts.headers);
+
+ var body = opts.body;
+ var req = support.cors ? new XMLHttpRequest() : new XDomainRequest();
+ var reqTimeout;
+ var timedOut;
+ var connected = false;
+
+ reqTimeout = setTimeout(onTimeout, opts.timeouts.connect);
+ // we set an empty onprogress listener
+ // so that XDomainRequest on IE9 is not aborted
+ // refs:
+ // - https://github.com/algolia/algoliasearch-client-js/issues/76
+ // - https://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified?forum=iewebdevelopment
+ req.onprogress = onProgress;
+ if ('onreadystatechange' in req) req.onreadystatechange = onReadyStateChange;
+ req.onload = onLoad;
+ req.onerror = onError;
+
+ // do not rely on default XHR async flag, as some analytics code like hotjar
+ // breaks it and set it to false by default
+ if (req instanceof XMLHttpRequest) {
+ req.open(opts.method, url, true);
+
+ // The Analytics API never accepts Auth headers as query string
+ // this option exists specifically for them.
+ if (opts.forceAuthHeaders) {
+ req.setRequestHeader(
+ 'x-algolia-application-id',
+ opts.headers['x-algolia-application-id']
+ );
+ req.setRequestHeader(
+ 'x-algolia-api-key',
+ opts.headers['x-algolia-api-key']
+ );
+ }
+ } else {
+ req.open(opts.method, url);
+ }
+
+ // headers are meant to be sent after open
+ if (support.cors) {
+ if (body) {
+ if (opts.method === 'POST') {
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Simple_requests
+ req.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
+ } else {
+ req.setRequestHeader('content-type', 'application/json');
+ }
+ }
+ req.setRequestHeader('accept', 'application/json');
+ }
+
+ if (body) {
+ req.send(body);
+ } else {
+ req.send();
+ }
+
+ // event object not received in IE8, at least
+ // but we do not use it, still important to note
+ function onLoad(/* event */) {
+ // When browser does not supports req.timeout, we can
+ // have both a load and timeout event, since handled by a dumb setTimeout
+ if (timedOut) {
+ return;
+ }
+
+ clearTimeout(reqTimeout);
+
+ var out;
+
+ try {
+ out = {
+ body: JSON.parse(req.responseText),
+ responseText: req.responseText,
+ statusCode: req.status,
+ // XDomainRequest does not have any response headers
+ headers: req.getAllResponseHeaders && req.getAllResponseHeaders() || {}
+ };
+ } catch (e) {
+ out = new errors.UnparsableJSON({
+ more: req.responseText
+ });
+ }
+
+ if (out instanceof errors.UnparsableJSON) {
+ reject(out);
+ } else {
+ resolve(out);
+ }
+ }
+
+ function onError(event) {
+ if (timedOut) {
+ return;
+ }
+
+ clearTimeout(reqTimeout);
+
+ // error event is trigerred both with XDR/XHR on:
+ // - DNS error
+ // - unallowed cross domain request
+ reject(
+ new errors.Network({
+ more: event
+ })
+ );
+ }
+
+ function onTimeout() {
+ timedOut = true;
+ req.abort();
+
+ reject(new errors.RequestTimeout());
+ }
+
+ function onConnect() {
+ connected = true;
+ clearTimeout(reqTimeout);
+ reqTimeout = setTimeout(onTimeout, opts.timeouts.complete);
+ }
+
+ function onProgress() {
+ if (!connected) onConnect();
+ }
+
+ function onReadyStateChange() {
+ if (!connected && req.readyState > 1) onConnect();
+ }
+ });
+ };
+
+ AlgoliaSearchBrowser.prototype._request.fallback = function requestFallback(url, opts) {
+ url = inlineHeaders(url, opts.headers);
+
+ return new Promise(function wrapJsonpRequest(resolve, reject) {
+ jsonpRequest(url, opts, function jsonpRequestDone(err, content) {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ resolve(content);
+ });
+ });
+ };
+
+ AlgoliaSearchBrowser.prototype._promise = {
+ reject: function rejectPromise(val) {
+ return Promise.reject(val);
+ },
+ resolve: function resolvePromise(val) {
+ return Promise.resolve(val);
+ },
+ delay: function delayPromise(ms) {
+ return new Promise(function resolveOnTimeout(resolve/* , reject*/) {
+ setTimeout(resolve, ms);
+ });
+ },
+ all: function all(promises) {
+ return Promise.all(promises);
+ }
+ };
+
+ return algoliasearch;
+};
diff --git a/node_modules/algoliasearch/src/browser/inline-headers.js b/node_modules/algoliasearch/src/browser/inline-headers.js
new file mode 100644
index 00000000..7339d38a
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/inline-headers.js
@@ -0,0 +1,15 @@
+'use strict';
+
+module.exports = inlineHeaders;
+
+var encode = require('querystring-es3/encode');
+
+function inlineHeaders(url, headers) {
+ if (/\?/.test(url)) {
+ url += '&';
+ } else {
+ url += '?';
+ }
+
+ return url + encode(headers);
+}
diff --git a/node_modules/algoliasearch/src/browser/jsonp-request.js b/node_modules/algoliasearch/src/browser/jsonp-request.js
new file mode 100644
index 00000000..1aed7631
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/jsonp-request.js
@@ -0,0 +1,126 @@
+'use strict';
+
+module.exports = jsonpRequest;
+
+var errors = require('../errors');
+
+var JSONPCounter = 0;
+
+function jsonpRequest(url, opts, cb) {
+ if (opts.method !== 'GET') {
+ cb(new Error('Method ' + opts.method + ' ' + url + ' is not supported by JSONP.'));
+ return;
+ }
+
+ opts.debug('JSONP: start');
+
+ var cbCalled = false;
+ var timedOut = false;
+
+ JSONPCounter += 1;
+ var head = document.getElementsByTagName('head')[0];
+ var script = document.createElement('script');
+ var cbName = 'algoliaJSONP_' + JSONPCounter;
+ var done = false;
+
+ window[cbName] = function(data) {
+ removeGlobals();
+
+ if (timedOut) {
+ opts.debug('JSONP: Late answer, ignoring');
+ return;
+ }
+
+ cbCalled = true;
+
+ clean();
+
+ cb(null, {
+ body: data,
+ responseText: JSON.stringify(data)/* ,
+ // We do not send the statusCode, there's no statusCode in JSONP, it will be
+ // computed using data.status && data.message like with XDR
+ statusCode*/
+ });
+ };
+
+ // add callback by hand
+ url += '&callback=' + cbName;
+
+ // add body params manually
+ if (opts.jsonBody && opts.jsonBody.params) {
+ url += '&' + opts.jsonBody.params;
+ }
+
+ var ontimeout = setTimeout(timeout, opts.timeouts.complete);
+
+ // script onreadystatechange needed only for
+ // <= IE8
+ // https://github.com/angular/angular.js/issues/4523
+ script.onreadystatechange = readystatechange;
+ script.onload = success;
+ script.onerror = error;
+
+ script.async = true;
+ script.defer = true;
+ script.src = url;
+ head.appendChild(script);
+
+ function success() {
+ opts.debug('JSONP: success');
+
+ if (done || timedOut) {
+ return;
+ }
+
+ done = true;
+
+ // script loaded but did not call the fn => script loading error
+ if (!cbCalled) {
+ opts.debug('JSONP: Fail. Script loaded but did not call the callback');
+ clean();
+ cb(new errors.JSONPScriptFail());
+ }
+ }
+
+ function readystatechange() {
+ if (this.readyState === 'loaded' || this.readyState === 'complete') {
+ success();
+ }
+ }
+
+ function clean() {
+ clearTimeout(ontimeout);
+ script.onload = null;
+ script.onreadystatechange = null;
+ script.onerror = null;
+ head.removeChild(script);
+ }
+
+ function removeGlobals() {
+ try {
+ delete window[cbName];
+ delete window[cbName + '_loaded'];
+ } catch (e) {
+ window[cbName] = window[cbName + '_loaded'] = undefined;
+ }
+ }
+
+ function timeout() {
+ opts.debug('JSONP: Script timeout');
+ timedOut = true;
+ clean();
+ cb(new errors.RequestTimeout());
+ }
+
+ function error() {
+ opts.debug('JSONP: Script error');
+
+ if (done || timedOut) {
+ return;
+ }
+
+ clean();
+ cb(new errors.JSONPScriptError());
+ }
+}
diff --git a/node_modules/algoliasearch/src/browser/migration-layer/is-using-latest.js b/node_modules/algoliasearch/src/browser/migration-layer/is-using-latest.js
new file mode 100644
index 00000000..f8e29d4e
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/migration-layer/is-using-latest.js
@@ -0,0 +1,23 @@
+'use strict';
+
+// this module helps finding if the current page is using
+// the cdn.jsdelivr.net/algoliasearch/latest/$BUILDNAME.min.js version
+
+module.exports = isUsingLatest;
+
+function isUsingLatest(buildName) {
+ var toFind = new RegExp('cdn\\.jsdelivr\\.net/algoliasearch/latest/' +
+ buildName.replace('.', '\\.') + // algoliasearch, algoliasearch.angular
+ '(?:\\.min)?\\.js$'); // [.min].js
+
+ var scripts = document.getElementsByTagName('script');
+ var found = false;
+ for (var currentScript = 0, nbScripts = scripts.length; currentScript < nbScripts; currentScript++) {
+ if (scripts[currentScript].src && toFind.test(scripts[currentScript].src)) {
+ found = true;
+ break;
+ }
+ }
+
+ return found;
+}
diff --git a/node_modules/algoliasearch/src/browser/migration-layer/load-v2.js b/node_modules/algoliasearch/src/browser/migration-layer/load-v2.js
new file mode 100644
index 00000000..b3470baa
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/migration-layer/load-v2.js
@@ -0,0 +1,50 @@
+'use strict';
+
+module.exports = loadV2;
+
+function loadV2(buildName) {
+ var loadScript = require('load-script');
+ var v2ScriptUrl = '//cdn.jsdelivr.net/algoliasearch/2/' + buildName + '.min.js';
+
+ var message = '-- AlgoliaSearch `latest` warning --\n' +
+ 'Warning, you are using the `latest` version string from jsDelivr to load the AlgoliaSearch library.\n' +
+ 'Using `latest` is no more recommended, you should load //cdn.jsdelivr.net/algoliasearch/2/algoliasearch.min.js\n\n' +
+ 'Also, we updated the AlgoliaSearch JavaScript client to V3. If you want to upgrade,\n' +
+ 'please read our migration guide at https://github.com/algolia/algoliasearch-client-js/wiki/Migration-guide-from-2.x.x-to-3.x.x\n' +
+ '-- /AlgoliaSearch `latest` warning --';
+
+ if (window.console) {
+ if (window.console.warn) {
+ window.console.warn(message);
+ } else if (window.console.log) {
+ window.console.log(message);
+ }
+ }
+
+ // If current script loaded asynchronously,
+ // it will load the script with DOMElement
+ // otherwise, it will load the script with document.write
+ try {
+ // why \x3c? http://stackoverflow.com/a/236106/147079
+ document.write('\x3Cscript>window.ALGOLIA_SUPPORTS_DOCWRITE = true\x3C/script>');
+
+ if (window.ALGOLIA_SUPPORTS_DOCWRITE === true) {
+ document.write('\x3Cscript src="' + v2ScriptUrl + '">\x3C/script>');
+ scriptLoaded('document.write')();
+ } else {
+ loadScript(v2ScriptUrl, scriptLoaded('DOMElement'));
+ }
+ } catch (e) {
+ loadScript(v2ScriptUrl, scriptLoaded('DOMElement'));
+ }
+}
+
+function scriptLoaded(method) {
+ return function log() {
+ var message = 'AlgoliaSearch: loaded V2 script using ' + method;
+
+ if (window.console && window.console.log) {
+ window.console.log(message);
+ }
+ };
+}
diff --git a/node_modules/algoliasearch/src/browser/migration-layer/old-globals.js b/node_modules/algoliasearch/src/browser/migration-layer/old-globals.js
new file mode 100644
index 00000000..1f40c0d2
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/migration-layer/old-globals.js
@@ -0,0 +1,26 @@
+'use strict';
+
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+module.exports = oldGlobals;
+
+// put old window.AlgoliaSearch.. into window. again so that
+// users upgrading to V3 without changing their code, will be warned
+function oldGlobals() {
+ var message = '-- AlgoliaSearch V2 => V3 error --\n' +
+ 'You are trying to use a new version of the AlgoliaSearch JavaScript client with an old notation.\n' +
+ 'Please read our migration guide at https://github.com/algolia/algoliasearch-client-js/wiki/Migration-guide-from-2.x.x-to-3.x.x\n' +
+ '-- /AlgoliaSearch V2 => V3 error --';
+
+ window.AlgoliaSearch = function() {
+ throw new Error(message);
+ };
+
+ window.AlgoliaSearchHelper = function() {
+ throw new Error(message);
+ };
+
+ window.AlgoliaExplainResults = function() {
+ throw new Error(message);
+ };
+}
diff --git a/node_modules/algoliasearch/src/browser/migration-layer/script.js b/node_modules/algoliasearch/src/browser/migration-layer/script.js
new file mode 100644
index 00000000..9d12315f
--- /dev/null
+++ b/node_modules/algoliasearch/src/browser/migration-layer/script.js
@@ -0,0 +1,25 @@
+'use strict';
+
+// This script will be browserified and prepended to the normal build
+// directly in window, not wrapped in any module definition
+// To avoid cases where we are loaded with /latest/ along with
+migrationLayer(process.env.ALGOLIA_BUILDNAME);
+
+// Now onto the V2 related code:
+// If the client is using /latest/$BUILDNAME.min.js, load V2 of the library
+//
+// Otherwise, setup a migration layer that will throw on old constructors like
+// new AlgoliaSearch().
+// So that users upgrading from v2 to v3 will have a clear information
+// message on what to do if they did not read the migration guide
+function migrationLayer(buildName) {
+ var isUsingLatest = require('./is-using-latest');
+ var loadV2 = require('./load-v2');
+ var oldGlobals = require('./old-globals');
+
+ if (isUsingLatest(buildName)) {
+ loadV2(buildName);
+ } else {
+ oldGlobals();
+ }
+}
diff --git a/node_modules/algoliasearch/src/buildSearchMethod.js b/node_modules/algoliasearch/src/buildSearchMethod.js
new file mode 100644
index 00000000..33b1d52b
--- /dev/null
+++ b/node_modules/algoliasearch/src/buildSearchMethod.js
@@ -0,0 +1,67 @@
+module.exports = buildSearchMethod;
+
+var errors = require('./errors.js');
+
+/**
+ * Creates a search method to be used in clients
+ * @param {string} queryParam the name of the attribute used for the query
+ * @param {string} url the url
+ * @return {function} the search method
+ */
+function buildSearchMethod(queryParam, url) {
+ /**
+ * The search method. Prepares the data and send the query to Algolia.
+ * @param {string} query the string used for query search
+ * @param {object} args additional parameters to send with the search
+ * @param {function} [callback] the callback to be called with the client gets the answer
+ * @return {undefined|Promise} If the callback is not provided then this methods returns a Promise
+ */
+ return function search(query, args, callback) {
+ // warn V2 users on how to search
+ if (typeof query === 'function' && typeof args === 'object' ||
+ typeof callback === 'object') {
+ // .search(query, params, cb)
+ // .search(cb, params)
+ throw new errors.AlgoliaSearchError('index.search usage is index.search(query, params, cb)');
+ }
+
+ // Normalizing the function signature
+ if (arguments.length === 0 || typeof query === 'function') {
+ // Usage : .search(), .search(cb)
+ callback = query;
+ query = '';
+ } else if (arguments.length === 1 || typeof args === 'function') {
+ // Usage : .search(query/args), .search(query, cb)
+ callback = args;
+ args = undefined;
+ }
+ // At this point we have 3 arguments with values
+
+ // Usage : .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 = '';
+ }
+
+ var params = '';
+
+ if (query !== undefined) {
+ params += queryParam + '=' + encodeURIComponent(query);
+ }
+
+ var additionalUA;
+ if (args !== undefined) {
+ if (args.additionalUA) {
+ additionalUA = args.additionalUA;
+ delete args.additionalUA;
+ }
+ // `_getSearchParams` will augment params, do not be fooled by the = versus += from previous if
+ params = this.as._getSearchParams(args, params);
+ }
+
+
+ return this._search(params, url, callback, additionalUA);
+ };
+}
diff --git a/node_modules/algoliasearch/src/clone.js b/node_modules/algoliasearch/src/clone.js
new file mode 100644
index 00000000..5e505560
--- /dev/null
+++ b/node_modules/algoliasearch/src/clone.js
@@ -0,0 +1,3 @@
+module.exports = function clone(obj) {
+ return JSON.parse(JSON.stringify(obj));
+};
diff --git a/node_modules/algoliasearch/src/createAnalyticsClient.js b/node_modules/algoliasearch/src/createAnalyticsClient.js
new file mode 100644
index 00000000..d2bf8005
--- /dev/null
+++ b/node_modules/algoliasearch/src/createAnalyticsClient.js
@@ -0,0 +1,85 @@
+module.exports = createAnalyticsClient;
+
+var algoliasearch = require('../index.js');
+
+function createAnalyticsClient(appId, apiKey, opts) {
+ var analytics = {};
+
+ opts = opts || {};
+ // there need to be 4 hosts, like on the client, since if requests fail,
+ // the counter goes up by 1, so we need to have the same amount of hosts
+ // 4 because: -dsn, -1, -2, -3
+ // This is done because the APPID used for search will be the same for the analytics client created,
+ // and since the state of available hosts is shared by APPID globally for the module, we had issues
+ // where the hostIndex would be 1 while the array was only one entry (you got an empty host)
+ opts.hosts = opts.hosts || [
+ 'analytics.algolia.com',
+ 'analytics.algolia.com',
+ 'analytics.algolia.com',
+ 'analytics.algolia.com'
+ ];
+ opts.protocol = opts.protocol || 'https:';
+
+ analytics.as = algoliasearch(appId, apiKey, opts);
+
+ analytics.getABTests = function(_params, callback) {
+ var params = params || {};
+ var offset = params.offset || 0;
+ var limit = params.limit || 10;
+
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/2/abtests?offset=' + encodeURIComponent(offset) + '&limit=' + encodeURIComponent(limit),
+ hostType: 'read',
+ forceAuthHeaders: true,
+ callback: callback
+ });
+ };
+
+ analytics.getABTest = function(abTestID, callback) {
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/2/abtests/' + encodeURIComponent(abTestID),
+ hostType: 'read',
+ forceAuthHeaders: true,
+ callback: callback
+ });
+ };
+
+ analytics.addABTest = function(abTest, callback) {
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/2/abtests',
+ body: abTest,
+ hostType: 'read',
+ forceAuthHeaders: true,
+ callback: callback
+ });
+ };
+
+ analytics.stopABTest = function(abTestID, callback) {
+ return this.as._jsonRequest({
+ method: 'POST',
+ url: '/2/abtests/' + encodeURIComponent(abTestID) + '/stop',
+ hostType: 'read',
+ forceAuthHeaders: true,
+ callback: callback
+ });
+ };
+
+ analytics.deleteABTest = function(abTestID, callback) {
+ return this.as._jsonRequest({
+ method: 'DELETE',
+ url: '/2/abtests/' + encodeURIComponent(abTestID),
+ hostType: 'write',
+ forceAuthHeaders: true,
+ callback: callback
+ });
+ };
+
+ analytics.waitTask = function(indexName, taskID, callback) {
+ return this.as.initIndex(indexName).waitTask(taskID, callback);
+ };
+
+ return analytics;
+}
diff --git a/node_modules/algoliasearch/src/deprecate.js b/node_modules/algoliasearch/src/deprecate.js
new file mode 100644
index 00000000..ff51a54b
--- /dev/null
+++ b/node_modules/algoliasearch/src/deprecate.js
@@ -0,0 +1,15 @@
+module.exports = function deprecate(fn, message) {
+ var warned = false;
+
+ function deprecated() {
+ if (!warned) {
+ /* eslint no-console:0 */
+ console.warn(message);
+ warned = true;
+ }
+
+ return fn.apply(this, arguments);
+ }
+
+ return deprecated;
+};
diff --git a/node_modules/algoliasearch/src/deprecatedMessage.js b/node_modules/algoliasearch/src/deprecatedMessage.js
new file mode 100644
index 00000000..3d809428
--- /dev/null
+++ b/node_modules/algoliasearch/src/deprecatedMessage.js
@@ -0,0 +1,7 @@
+module.exports = function deprecatedMessage(previousUsage, newUsage) {
+ var githubAnchorLink = previousUsage.toLowerCase()
+ .replace(/[\.\(\)]/g, '');
+
+ return 'algoliasearch: `' + previousUsage + '` was replaced by `' + newUsage +
+ '`. Please see https://github.com/algolia/algoliasearch-client-javascript/wiki/Deprecated#' + githubAnchorLink;
+};
diff --git a/node_modules/algoliasearch/src/errors.js b/node_modules/algoliasearch/src/errors.js
new file mode 100644
index 00000000..31154503
--- /dev/null
+++ b/node_modules/algoliasearch/src/errors.js
@@ -0,0 +1,78 @@
+'use strict';
+
+// This file hosts our error definitions
+// We use custom error "types" so that we can act on them when we need it
+// e.g.: if error instanceof errors.UnparsableJSON then..
+
+var inherits = require('inherits');
+
+function AlgoliaSearchError(message, extraProperties) {
+ var forEach = require('foreach');
+
+ var error = this;
+
+ // try to get a stacktrace
+ if (typeof Error.captureStackTrace === 'function') {
+ Error.captureStackTrace(this, this.constructor);
+ } else {
+ error.stack = (new Error()).stack || 'Cannot get a stacktrace, browser is too old';
+ }
+
+ this.name = 'AlgoliaSearchError';
+ this.message = message || 'Unknown error';
+
+ if (extraProperties) {
+ forEach(extraProperties, function addToErrorObject(value, key) {
+ error[key] = value;
+ });
+ }
+}
+
+inherits(AlgoliaSearchError, Error);
+
+function createCustomError(name, message) {
+ function AlgoliaSearchCustomError() {
+ var args = Array.prototype.slice.call(arguments, 0);
+
+ // custom message not set, use default
+ if (typeof args[0] !== 'string') {
+ args.unshift(message);
+ }
+
+ AlgoliaSearchError.apply(this, args);
+ this.name = 'AlgoliaSearch' + name + 'Error';
+ }
+
+ inherits(AlgoliaSearchCustomError, AlgoliaSearchError);
+
+ return AlgoliaSearchCustomError;
+}
+
+// late exports to let various fn defs and inherits take place
+module.exports = {
+ AlgoliaSearchError: AlgoliaSearchError,
+ UnparsableJSON: createCustomError(
+ 'UnparsableJSON',
+ 'Could not parse the incoming response as JSON, see err.more for details'
+ ),
+ RequestTimeout: createCustomError(
+ 'RequestTimeout',
+ 'Request timedout before getting a response'
+ ),
+ Network: createCustomError(
+ 'Network',
+ 'Network issue, see err.more for details'
+ ),
+ JSONPScriptFail: createCustomError(
+ 'JSONPScriptFail',
+ '<script> was loaded but did not call our provided callback'
+ ),
+ JSONPScriptError: createCustomError(
+ 'JSONPScriptError',
+ '<script> unable to load due to an `error` event on it'
+ ),
+ Unknown: createCustomError(
+ 'Unknown',
+ 'Unknown error occured'
+ )
+};
diff --git a/node_modules/algoliasearch/src/exitPromise.js b/node_modules/algoliasearch/src/exitPromise.js
new file mode 100644
index 00000000..4c6cd042
--- /dev/null
+++ b/node_modules/algoliasearch/src/exitPromise.js
@@ -0,0 +1,7 @@
+// Parse cloud does not supports setTimeout
+// We do not store a setTimeout reference in the client everytime
+// We only fallback to a fake setTimeout when not available
+// setTimeout cannot be override globally sadly
+module.exports = function exitPromise(fn, _setTimeout) {
+ _setTimeout(fn, 0);
+};
diff --git a/node_modules/algoliasearch/src/map.js b/node_modules/algoliasearch/src/map.js
new file mode 100644
index 00000000..f9472eb4
--- /dev/null
+++ b/node_modules/algoliasearch/src/map.js
@@ -0,0 +1,9 @@
+var foreach = require('foreach');
+
+module.exports = function map(arr, fn) {
+ var newArr = [];
+ foreach(arr, function(item, itemIndex) {
+ newArr.push(fn(item, itemIndex, arr));
+ });
+ return newArr;
+};
diff --git a/node_modules/algoliasearch/src/merge.js b/node_modules/algoliasearch/src/merge.js
new file mode 100644
index 00000000..99a32d75
--- /dev/null
+++ b/node_modules/algoliasearch/src/merge.js
@@ -0,0 +1,19 @@
+var foreach = require('foreach');
+
+module.exports = function merge(destination/* , sources */) {
+ var sources = Array.prototype.slice.call(arguments);
+
+ foreach(sources, function(source) {
+ for (var keyName in source) {
+ if (source.hasOwnProperty(keyName)) {
+ if (typeof destination[keyName] === 'object' && typeof source[keyName] === 'object') {
+ destination[keyName] = merge({}, destination[keyName], source[keyName]);
+ } else if (source[keyName] !== undefined) {
+ destination[keyName] = source[keyName];
+ }
+ }
+ }
+ });
+
+ return destination;
+};
diff --git a/node_modules/algoliasearch/src/omit.js b/node_modules/algoliasearch/src/omit.js
new file mode 100644
index 00000000..9b1b5d17
--- /dev/null
+++ b/node_modules/algoliasearch/src/omit.js
@@ -0,0 +1,14 @@
+module.exports = function omit(obj, test) {
+ var keys = require('object-keys');
+ var foreach = require('foreach');
+
+ var filtered = {};
+
+ foreach(keys(obj), function doFilter(keyName) {
+ if (test(keyName) !== true) {
+ filtered[keyName] = obj[keyName];
+ }
+ });
+
+ return filtered;
+};
diff --git a/node_modules/algoliasearch/src/places.js b/node_modules/algoliasearch/src/places.js
new file mode 100644
index 00000000..9e8d5bd2
--- /dev/null
+++ b/node_modules/algoliasearch/src/places.js
@@ -0,0 +1,37 @@
+module.exports = createPlacesClient;
+
+var buildSearchMethod = require('./buildSearchMethod.js');
+
+function createPlacesClient(algoliasearch) {
+ return function places(appID, apiKey, opts) {
+ var cloneDeep = require('./clone.js');
+
+ opts = opts && cloneDeep(opts) || {};
+ opts.hosts = opts.hosts || [
+ 'places-dsn.algolia.net',
+ 'places-1.algolianet.com',
+ 'places-2.algolianet.com',
+ 'places-3.algolianet.com'
+ ];
+
+ // allow initPlaces() no arguments => community rate limited
+ if (arguments.length === 0 || typeof appID === 'object' || appID === undefined) {
+ appID = '';
+ apiKey = '';
+ opts._allowEmptyCredentials = true;
+ }
+
+ var client = algoliasearch(appID, apiKey, opts);
+ var index = client.initIndex('places');
+ index.search = buildSearchMethod('query', '/1/places/query');
+ index.getObject = function(objectID, callback) {
+ return this.as._jsonRequest({
+ method: 'GET',
+ url: '/1/places/' + encodeURIComponent(objectID),
+ hostType: 'read',
+ callback: callback
+ });
+ };
+ return index;
+ };
+}
diff --git a/node_modules/algoliasearch/src/reactnative/builds/algoliasearch.reactnative.js b/node_modules/algoliasearch/src/reactnative/builds/algoliasearch.reactnative.js
new file mode 100644
index 00000000..e3ab5e88
--- /dev/null
+++ b/node_modules/algoliasearch/src/reactnative/builds/algoliasearch.reactnative.js
@@ -0,0 +1,175 @@
+'use strict';
+
+// This is the standalone browser build entry point
+// Browser implementation of the Algolia Search JavaScript client,
+// using XMLHttpRequest for React Native
+module.exports = algoliasearch;
+
+var inherits = require('inherits');
+var Promise = window.Promise || require('es6-promise').Promise;
+
+var AlgoliaSearch = require('../../AlgoliaSearch');
+var errors = require('../../errors');
+var inlineHeaders = require('../../browser/inline-headers');
+var places = require('../../places.js');
+
+function algoliasearch(applicationID, apiKey, opts) {
+ var cloneDeep = require('../../clone.js');
+
+ opts = cloneDeep(opts || {});
+
+ if (opts.protocol === undefined) {
+ opts.protocol = 'https:';
+ }
+
+ opts._ua = opts._ua || algoliasearch.ua;
+
+ opts.timeouts = opts.timeouts || {
+ connect: 2 * 1000,
+ read: 3 * 1000,
+ write: 30 * 1000
+ };
+
+ return new AlgoliaSearchReactNative(applicationID, apiKey, opts);
+}
+
+algoliasearch.version = require('../../version.js');
+algoliasearch.ua = 'Algolia for ReactNative ' + algoliasearch.version;
+algoliasearch.initPlaces = places(algoliasearch);
+
+// we expose into window no matter how we are used, this will allow
+// us to easily debug any website running algolia
+window.__algolia = {
+ debug: require('debug'),
+ algoliasearch: algoliasearch
+};
+
+var support = {
+ timeout: 'timeout' in new XMLHttpRequest()
+};
+
+function AlgoliaSearchReactNative() {
+ // call AlgoliaSearch constructor
+ AlgoliaSearch.apply(this, arguments);
+}
+
+inherits(AlgoliaSearchReactNative, AlgoliaSearch);
+
+AlgoliaSearchReactNative.prototype._request = function request(url, opts) {
+ return new Promise(function wrapRequest(resolve, reject) {
+ url = inlineHeaders(url, opts.headers);
+
+ var body = opts.body;
+ var req = new XMLHttpRequest();
+ var ontimeout;
+ var timedOut;
+
+ // do not rely on default XHR async flag, as some analytics code like hotjar
+ // breaks it and set it to false by default
+ if (req instanceof XMLHttpRequest) {
+ req.open(opts.method, url, true);
+ } else {
+ req.open(opts.method, url);
+ }
+
+ if (body) {
+ req.setRequestHeader('content-type', 'application/json');
+ }
+
+ req.onload = load;
+ req.onerror = error;
+
+ if (support.timeout) {
+ // .timeout not supported at the time of this implementation. Maybe in the
+ // future...
+ req.timeout = opts.timeouts.complete;
+
+ req.ontimeout = timeout;
+ } else {
+ ontimeout = setTimeout(timeout, opts.timeouts.complete);
+ }
+
+ req.send(body);
+
+ // event object not received in IE8, at least
+ // but we do not use it, still important to note
+ function load(/* event */) {
+ // When browser does not supports req.timeout, we can
+ // have both a load and timeout event, since handled by a dumb setTimeout
+ if (timedOut) {
+ return;
+ }
+
+ if (!support.timeout) {
+ clearTimeout(ontimeout);
+ }
+
+ var out;
+
+ try {
+ out = {
+ body: JSON.parse(req.responseText),
+ responseText: req.responseText,
+ statusCode: req.status,
+ // XDomainRequest does not have any response headers
+ headers: req.getAllResponseHeaders && req.getAllResponseHeaders() || {}
+ };
+ } catch (e) {
+ out = new errors.UnparsableJSON({
+ more: req.responseText
+ });
+ }
+
+ if (out instanceof errors.UnparsableJSON) {
+ reject(out);
+ } else {
+ resolve(out);
+ }
+ }
+
+ function error(event) {
+ if (timedOut) {
+ return;
+ }
+
+ if (!support.timeout) {
+ clearTimeout(ontimeout);
+ }
+
+ // error event is trigerred both with XDR/XHR on:
+ // - DNS error
+ // - unallowed cross domain request
+ reject(
+ new errors.Network({
+ more: event
+ })
+ );
+ }
+
+ function timeout() {
+ if (!support.timeout) {
+ timedOut = true;
+ req.abort();
+ }
+
+ reject(new errors.RequestTimeout());
+ }
+ });
+};
+
+AlgoliaSearchReactNative.prototype._promise = {
+ reject: function rejectPromise(val) {
+ return Promise.reject(val);
+ },
+ resolve: function resolvePromise(val) {
+ return Promise.resolve(val);
+ },
+ delay: function delayPromise(ms) {
+ return new Promise(function resolveOnTimeout(resolve/* , reject*/) {
+ setTimeout(resolve, ms);
+ });
+ },
+ all: function all(promises) {
+ return Promise.all(promises);
+ }
+};
diff --git a/node_modules/algoliasearch/src/server/builds/AlgoliaSearchServer.js b/node_modules/algoliasearch/src/server/builds/AlgoliaSearchServer.js
new file mode 100644
index 00000000..c6e5dfec
--- /dev/null
+++ b/node_modules/algoliasearch/src/server/builds/AlgoliaSearchServer.js
@@ -0,0 +1,78 @@
+'use strict';
+
+// Some methods only accessible server side
+
+module.exports = AlgoliaSearchServer;
+
+var inherits = require('inherits');
+
+var AlgoliaSearch = require('../../AlgoliaSearch');
+
+function AlgoliaSearchServer(applicationID, apiKey, opts) {
+ // Default protocol is https: on the server, to avoid leaking admin keys
+ if (opts.protocol === undefined) {
+ opts.protocol = 'https:';
+ }
+
+ AlgoliaSearch.apply(this, arguments);
+}
+
+inherits(AlgoliaSearchServer, AlgoliaSearch);
+
+/*
+ * Allow to use IP rate limit when you have a proxy between end-user and Algolia.
+ * This option will set the X-Forwarded-For HTTP header with the client IP and the X-Forwarded-API-Key with the API Key having rate limits.
+ * @param adminAPIKey the admin API Key you can find in your dashboard
+ * @param endUserIP the end user IP (you can use both IPV4 or IPV6 syntax)
+ * @param rateLimitAPIKey the API key on which you have a rate limit
+ */
+AlgoliaSearchServer.prototype.enableRateLimitForward = function(adminAPIKey, endUserIP, rateLimitAPIKey) {
+ this._forward = {
+ adminAPIKey: adminAPIKey,
+ endUserIP: endUserIP,
+ rateLimitAPIKey: rateLimitAPIKey
+ };
+};
+
+/*
+ * Disable IP rate limit enabled with enableRateLimitForward() function
+ */
+AlgoliaSearchServer.prototype.disableRateLimitForward = function() {
+ this._forward = null;
+};
+
+/*
+ * Specify the securedAPIKey to use with associated information
+ */
+AlgoliaSearchServer.prototype.useSecuredAPIKey = function(securedAPIKey, securityTags, userToken) {
+ this._secure = {
+ apiKey: securedAPIKey,
+ securityTags: securityTags,
+ userToken: userToken
+ };
+};
+
+/*
+ * If a secured API was used, disable it
+ */
+AlgoliaSearchServer.prototype.disableSecuredAPIKey = function() {
+ this._secure = null;
+};
+
+AlgoliaSearchServer.prototype._computeRequestHeaders = function(additionalUA) {
+ var headers = AlgoliaSearchServer.super_.prototype._computeRequestHeaders.call(this, additionalUA);
+
+ if (this._forward) {
+ headers['x-algolia-api-key'] = this._forward.adminAPIKey;
+ headers['x-forwarded-for'] = this._forward.endUserIP;
+ headers['x-forwarded-api-key'] = this._forward.rateLimitAPIKey;
+ }
+
+ if (this._secure) {
+ headers['x-algolia-api-key'] = this._secure.apiKey;
+ headers['x-algolia-tagfilters'] = this._secure.securityTags;
+ headers['x-algolia-usertoken'] = this._secure.userToken;
+ }
+
+ return headers;
+};
diff --git a/node_modules/algoliasearch/src/server/builds/get-agent.js b/node_modules/algoliasearch/src/server/builds/get-agent.js
new file mode 100644
index 00000000..cbe003b4
--- /dev/null
+++ b/node_modules/algoliasearch/src/server/builds/get-agent.js
@@ -0,0 +1,107 @@
+'use strict';
+
+// this module will either get a regular http keepalive agent or
+// a proxying agent if `HTTP_PROXY` or `HTTPS_PROXY` environment variables
+// are set
+// request/request module could have been used but it does not do any
+// keepalive on POST requests https://github.com/request/request/issues/236#issuecomment-94587587
+
+module.exports = getAgent;
+
+function getAgent(protocol) {
+ var agent;
+
+ if (protocol !== 'http:' && protocol !== 'https:') {
+ throw new Error('get-agent: `protocol` must be `http:` or `https:`');
+ }
+
+ var parsedProxy = getParsedProxy();
+
+ if (parsedProxy.protocol) {
+ agent = getProxyingAgent(protocol, parsedProxy);
+ } else {
+ // no proxy given, let's use regular keepalived agents
+ agent = getKeepaliveAgent(protocol);
+ }
+
+ return agent;
+}
+
+function getParsedProxy() {
+ var url = require('url');
+ var proxy = process.env.HTTP_PROXY ||
+ process.env.HTTPS_PROXY ||
+ '';
+
+ return url.parse(proxy);
+}
+
+function getKeepaliveAgent(protocol) {
+ var http = require('http');
+ var https = require('https');
+
+ var HttpsAgent = require('agentkeepalive').HttpsAgent;
+ var HttpAgent = require('agentkeepalive');
+ var semver = require('semver');
+
+ var keepAliveAgent;
+
+ // node 0.10 => agentkeepalive
+ // node >= 0.12 => native keepalive
+ // iojs => native keepalive
+ // node >= 0.11.4 has good keepAlive https://github.com/joyent/node/commit/b5b841
+ if (semver.satisfies(process.version, '<0.11.4')) {
+ if (protocol === 'http:') {
+ keepAliveAgent = new HttpAgent({
+ maxSockets: Infinity
+ });
+ } else if (protocol === 'https:') {
+ keepAliveAgent = new HttpsAgent({
+ maxSockets: Infinity
+ });
+ }
+ } else if (protocol === 'http:') {
+ keepAliveAgent = new http.Agent({
+ keepAlive: true,
+ maxSockets: Infinity
+ });
+ } else if (protocol === 'https:') {
+ keepAliveAgent = new https.Agent({
+ keepAlive: true,
+ maxSockets: Infinity
+ });
+ }
+
+ return keepAliveAgent;
+}
+
+function getProxyingAgent(protocol, parsedProxy) {
+ var tunnel = require('tunnel-agent');
+
+ var agentSettings = {
+ maxSockets: Infinity,
+ proxy: {
+ host: parsedProxy.hostname,
+ port: parseInt(parsedProxy.port, 10),
+ proxyAuth: parsedProxy.auth
+ }
+ };
+
+ // httpOverHttps
+ // httpsOverHttps
+ // https://github.com/mikeal/tunnel-agent/blob/912a7a6d00e10ec76baf9c9369de280fa5badef3/index.js#L12-L15
+ var tunnelType = protocol.replace(':', '') +
+ 'Over' +
+ parsedProxy.protocol.replace(':', '').replace('h', 'H');
+
+ var agent = new tunnel[tunnelType](agentSettings);
+
+ // `tunnel-agent` does not have a destroy method
+ agent.destroy = function() {
+ this.sockets.forEach(function(socket) {
+ socket.destroy();
+ });
+ };
+
+ return agent;
+}
diff --git a/node_modules/algoliasearch/src/server/builds/node.js b/node_modules/algoliasearch/src/server/builds/node.js
new file mode 100644
index 00000000..11b806ff
--- /dev/null
+++ b/node_modules/algoliasearch/src/server/builds/node.js
@@ -0,0 +1,318 @@
+'use strict';
+
+// This is the Node.JS entry point
+module.exports = algoliasearch;
+
+var debug = require('debug')('algoliasearch:nodejs');
+var crypto = require('crypto');
+var zlib = require('zlib');
+
+var inherits = require('inherits');
+var Promise = global.Promise || require('es6-promise').Promise;
+var semver = require('semver');
+var isNotSupported = semver.satisfies(process.version, '<0.10');
+var isNode010 = semver.satisfies(process.version, '=0.10');
+var places = require('../../places.js');
+
+var AlgoliaSearchServer = require('./AlgoliaSearchServer');
+var errors = require('../../errors');
+
+// does not work on node <= 0.8
+if (isNotSupported) {
+ throw new errors.AlgoliaSearchError('Node.js version ' + process.version + ' is not supported');
+}
+
+if (process.env.NODE_ENV === 'debug') {
+ require('debug').enable('algoliasearch*');
+}
+
+debug('loaded the Node.js client');
+
+function algoliasearch(applicationID, apiKey, opts) {
+ var cloneDeep = require('../../clone.js');
+ var reduce = require('reduce');
+
+ if (!opts) {
+ opts = {};
+ }
+
+ var httpAgent = opts.httpAgent;
+
+ opts = cloneDeep(reduce(opts, allButHttpAgent, {}));
+
+ // as an httpAgent is an object with methods etc, we take a reference to
+ // it rather than cloning it like other values
+ function allButHttpAgent(filteredOpts, val, keyName) {
+ if (keyName !== 'httpAgent') {
+ filteredOpts[keyName] = val;
+ }
+
+ return filteredOpts;
+ }
+
+ opts.httpAgent = httpAgent;
+
+ opts.timeouts = opts.timeouts || {
+ connect: 2 * 1000,
+ read: 5 * 1000,
+ write: 30 * 1000
+ };
+
+ if (opts.protocol === undefined) {
+ opts.protocol = 'https:';
+ }
+
+ opts._ua = opts._ua || algoliasearch.ua;
+ opts._useCache = false;
+
+ return new AlgoliaSearchNodeJS(applicationID, apiKey, opts);
+}
+
+algoliasearch.version = require('../../version.js');
+algoliasearch.ua = 'Algolia for Node.js ' + algoliasearch.version;
+algoliasearch.initPlaces = places(algoliasearch);
+
+function AlgoliaSearchNodeJS(applicationID, apiKey, opts) {
+ var getAgent = require('./get-agent');
+
+ // call AlgoliaSearchServer constructor
+ AlgoliaSearchServer.apply(this, arguments);
+
+ this._Agent = opts.httpAgent || getAgent(opts.protocol);
+}
+
+inherits(AlgoliaSearchNodeJS, AlgoliaSearchServer);
+
+AlgoliaSearchNodeJS.prototype._request = function request(rawUrl, opts) {
+ var http = require('http');
+ var https = require('https');
+ var url = require('url');
+
+ var client = this;
+
+ return new Promise(function doReq(resolve, reject) {
+ opts.debug('url: %s, method: %s, timeouts: %j', rawUrl, opts.method, opts.timeouts);
+
+ var body = opts.body;
+
+ var parsedUrl = url.parse(rawUrl);
+ var requestOptions = {
+ hostname: parsedUrl.hostname,
+ port: parsedUrl.port,
+ method: opts.method,
+ path: parsedUrl.path,
+ agent: client._Agent
+ };
+
+ var timedOut = false;
+ var timeoutId;
+ var req;
+
+ if (parsedUrl.protocol === 'https:') {
+ // we do not rely on any "smart" port computing by either node.js
+ // or a custom http agent, because:
+ // https://github.com/TooTallNate/node-https-proxy-agent/issues/7#issuecomment-119539690
+ if (requestOptions.port === null) {
+ requestOptions.port = 443;
+ }
+ req = https.request(requestOptions);
+ } else {
+ // same reason to set the port as `https:`
+ if (requestOptions.port === null) {
+ requestOptions.port = 80;
+ }
+ req = http.request(requestOptions);
+ }
+
+ req.setHeader('connection', 'keep-alive');
+ req.setHeader('accept', 'application/json');
+
+ Object.keys(opts.headers).forEach(function setRequestHeader(headerName) {
+ req.setHeader(headerName, opts.headers[headerName]);
+ });
+
+ req.setHeader('accept-encoding', 'gzip,deflate');
+
+ // we do not use req.setTimeout because it's either an inactivity timeout
+ // or a global timeout given the nodejs version
+ timeoutId = setTimeout(timeout, opts.timeouts.connect);
+
+ req.once('error', error);
+ req.once('response', response);
+ if (body) {
+ req.setHeader('content-type', 'application/json');
+ req.setHeader('content-length', Buffer.byteLength(body, 'utf8'));
+ req.write(body);
+ } else if (req.method === 'DELETE') {
+ // Node.js was setting transfer-encoding: chunked on all DELETE requests
+ // which is not good since there's no body to be sent, resulting in nginx
+ // sending 400 on socket reuse (waiting for previous socket data)
+ // https://github.com/nodejs/node-v0.x-archive/issues/6164
+ // https://github.com/nodejs/node-v0.x-archive/commit/aef0960
+ req.setHeader('content-length', 0);
+ }
+
+ req.end();
+
+ function response(res) {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(onCompleteTimeout, opts.timeouts.complete);
+ var chunks = [];
+ var originalRes = res;
+
+ // save headers and statusCode BEFORE treating the response as zlib, otherwise
+ // we lose them
+ var headers = res.headers;
+ var statusCode = res.statusCode;
+
+ // Algolia answers should be gzip when asked for it,
+ // but a proxy might uncompress Algolia response
+ // So we handle both compressed and uncompressed
+ if (headers['content-encoding'] === 'gzip' ||
+ headers['content-encoding'] === 'deflate') {
+ res = res.pipe(zlib.createUnzip());
+ }
+
+ res
+ .on('data', onData)
+ .once('error', reject)
+ .once('end', onEnd);
+
+ function onData(chunk) {
+ chunks.push(chunk);
+ }
+
+ function onEnd() {
+ clearTimeout(timeoutId);
+
+ var data = Buffer.concat(chunks).toString();
+ var out;
+
+ try {
+ out = {
+ body: JSON.parse(data),
+ responseText: data,
+ statusCode: statusCode,
+ headers: headers
+ };
+ } catch (e) {
+ out = new errors.UnparsableJSON({
+ more: data
+ });
+ }
+
+ if (out instanceof errors.UnparsableJSON) {
+ reject(out);
+ } else {
+ resolve(out);
+ }
+ }
+
+ function onCompleteTimeout() {
+ res.removeListener('data', onData);
+ res.removeListener('end', onEnd);
+ originalRes.destroy();
+ timeout();
+ }
+ }
+
+ function error(err) {
+ opts.debug('error: %j - %s', err, rawUrl);
+
+ if (timedOut) {
+ opts.debug('request had already timedout');
+ return;
+ }
+
+ abort();
+ clearTimeout(timeoutId);
+ reject(new errors.Network(err.message, err));
+ }
+
+ function timeout() {
+ timedOut = true;
+ opts.debug('timeout %s', rawUrl);
+ abort();
+ reject(new errors.RequestTimeout());
+ }
+
+ function abort() {
+ if (isNode010 && req.socket && req.socket.socket) {
+ req.socket.socket.destroy();
+ }
+
+ req.removeListener('response', response);
+ req.abort();
+ }
+ });
+};
+
+AlgoliaSearchNodeJS.prototype._promise = {
+ reject: function reject(val) {
+ return Promise.reject(val);
+ },
+ resolve: function resolve(val) {
+ return Promise.resolve(val);
+ },
+ delay: function delayPromise(ms) {
+ return new Promise(function resolveOnTimeout(resolve/* , reject */) {
+ setTimeout(resolve, ms);
+ });
+ },
+ all: function all(promises) {
+ return Promise.all(promises);
+ }
+};
+
+AlgoliaSearchNodeJS.prototype.destroy = function destroy() {
+ if (typeof this._Agent.destroy === 'function') {
+ this._Agent.destroy();
+ }
+};
+
+/*
+ * Generate a secured and public API Key from an apiKey and queryParameters
+ * optional user token identifying the current user
+ *
+ * @param apiKey - The api key to encode as secure
+ * @param {Object} [queryParameters] - Any search query parameter
+ */
+AlgoliaSearchNodeJS.prototype.generateSecuredApiKey = function generateSecuredApiKey(privateApiKey, queryParametersOrTagFilters, userToken) {
+ var searchParams;
+
+ if (Array.isArray(queryParametersOrTagFilters)) {
+ // generateSecuredApiKey(apiKey, ['user_42'], userToken);
+
+ searchParams = {
+ tagFilters: queryParametersOrTagFilters
+ };
+
+ if (userToken) {
+ searchParams.userToken = userToken;
+ }
+
+ searchParams = this._getSearchParams(searchParams, '');
+ } else if (typeof queryParametersOrTagFilters === 'string') {
+ if (queryParametersOrTagFilters.indexOf('=') === -1) {
+ // generateSecuredApiKey(apiKey, 'user_42', userToken);
+ searchParams = 'tagFilters=' + queryParametersOrTagFilters;
+ } else {
+ // generateSecuredApiKey(apiKey, 'tagFilters=user_42', userToken);
+ searchParams = queryParametersOrTagFilters;
+ }
+
+
+ if (userToken) {
+ searchParams += '&userToken=' + encodeURIComponent(userToken);
+ }
+ } else {
+ searchParams = this._getSearchParams(queryParametersOrTagFilters, '');
+ }
+
+ var securedKey = crypto
+ .createHmac('sha256', privateApiKey)
+ .update(searchParams)
+ .digest('hex');
+
+ return new Buffer(securedKey + searchParams).toString('base64');
+};
diff --git a/node_modules/algoliasearch/src/store.js b/node_modules/algoliasearch/src/store.js
new file mode 100644
index 00000000..a84c9c05
--- /dev/null
+++ b/node_modules/algoliasearch/src/store.js
@@ -0,0 +1,86 @@
+var debug = require('debug')('algoliasearch:src/hostIndexState.js');
+var localStorageNamespace = 'algoliasearch-client-js';
+
+var store;
+var moduleStore = {
+ state: {},
+ set: function(key, data) {
+ this.state[key] = data;
+ return this.state[key];
+ },
+ get: function(key) {
+ return this.state[key] || null;
+ }
+};
+
+var localStorageStore = {
+ set: function(key, data) {
+ moduleStore.set(key, data); // always replicate localStorageStore to moduleStore in case of failure
+
+ try {
+ var namespace = JSON.parse(global.localStorage[localStorageNamespace]);
+ namespace[key] = data;
+ global.localStorage[localStorageNamespace] = JSON.stringify(namespace);
+ return namespace[key];
+ } catch (e) {
+ return localStorageFailure(key, e);
+ }
+ },
+ get: function(key) {
+ try {
+ return JSON.parse(global.localStorage[localStorageNamespace])[key] || null;
+ } catch (e) {
+ return localStorageFailure(key, e);
+ }
+ }
+};
+
+function localStorageFailure(key, e) {
+ debug('localStorage failed with', e);
+ cleanup();
+ store = moduleStore;
+ return store.get(key);
+}
+
+store = supportsLocalStorage() ? localStorageStore : moduleStore;
+
+module.exports = {
+ get: getOrSet,
+ set: getOrSet,
+ supportsLocalStorage: supportsLocalStorage
+};
+
+function getOrSet(key, data) {
+ if (arguments.length === 1) {
+ return store.get(key);
+ }
+
+ return store.set(key, data);
+}
+
+function supportsLocalStorage() {
+ try {
+ if ('localStorage' in global &&
+ global.localStorage !== null) {
+ if (!global.localStorage[localStorageNamespace]) {
+ // actual creation of the namespace
+ global.localStorage.setItem(localStorageNamespace, JSON.stringify({}));
+ }
+ return true;
+ }
+
+ return false;
+ } catch (_) {
+ return false;
+ }
+}
+
+// In case of any error on localStorage, we clean our own namespace, this should handle
+// quota errors when a lot of keys + data are used
+function cleanup() {
+ try {
+ global.localStorage.removeItem(localStorageNamespace);
+ } catch (_) {
+ // nothing to do
+ }
+}
diff --git a/node_modules/algoliasearch/src/version.js b/node_modules/algoliasearch/src/version.js
new file mode 100644
index 00000000..fbf29a51
--- /dev/null
+++ b/node_modules/algoliasearch/src/version.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = '3.30.0';