diff --git a/components/RefinementList/RefinementList.js b/components/RefinementList/RefinementList.js index ce8bd59f674..e36c525bcbe 100644 --- a/components/RefinementList/RefinementList.js +++ b/components/RefinementList/RefinementList.js @@ -35,7 +35,14 @@ class RefinementList extends React.Component { [this.props.cssClasses.active]: facetValue.isRefined }); - let key = facetValue[this.props.facetNameKey] + '/' + facetValue.isRefined + '/' + facetValue.count; + let key = facetValue[this.props.facetNameKey]; + if (facetValue.count !== undefined) { + key += '/' + facetValue.isRefined; + } + + if (facetValue.count !== undefined) { + key += '/' + facetValue.count; + } return (
{
); - expect(out.props.children[0].key).toEqual('facet1/undefined/undefined'); - expect(out.props.children[1].key).toEqual('facet2/undefined/undefined'); + expect(out.props.children[0].key).toEqual('facet1'); + expect(out.props.children[1].key).toEqual('facet2'); }); it('should render default list highlighted', () => { diff --git a/dev/app.js b/dev/app.js index 9f50258ab21..c676d2f901f 100644 --- a/dev/app.js +++ b/dev/app.js @@ -92,6 +92,30 @@ search.addWidget( }) ); +search.addWidget( + instantsearch.widgets.numericRefinementList({ + container: '#price-numeric-list', + attributeName: 'price', + operator: 'or', + options: [ + {name: 'All'}, + {end: 4, name: 'less than 4'}, + {start: 4, end: 4, name: '4'}, + {start: 5, end: 10, name: 'between 5 and 10'}, + {start: 10, name: 'more than 10'} + ], + cssClasses: { + header: 'facet-title', + link: 'facet-value', + count: 'facet-count pull-right', + active: 'facet-active' + }, + templates: { + header: 'Price numeric list' + } + }) +); + search.addWidget( instantsearch.widgets.refinementList({ container: '#price-range', @@ -187,4 +211,22 @@ search.once('render', function() { document.querySelector('.search').className = 'row search search--visible'; }); +search.addWidget( + instantsearch.widgets.priceRanges({ + container: '#price-ranges', + attributeName: 'price', + templates: { + header: 'Price ranges' + }, + cssClasses: { + header: 'facet-title', + body: 'nav nav-stacked', + range: 'facet-value', + form: '', + input: 'fixed-input-sm', + button: 'btn btn-default btn-sm' + } + }) +); + search.start(); diff --git a/dev/index.html b/dev/index.html index a95f7e420e7..01025cb4b8f 100644 --- a/dev/index.html +++ b/dev/index.html @@ -28,6 +28,7 @@

Instant search demo using instantsearch.js
+
diff --git a/dev/style.css b/dev/style.css index 311d79f57a9..e8c0969d3a7 100644 --- a/dev/style.css +++ b/dev/style.css @@ -110,3 +110,7 @@ body { .ais-price-ranges .ais-price-ranges--input{ width: 65px; } + +.ais-refinement-list--label input[type=radio]{ + margin: 4px 8px 0 0; +} diff --git a/docs/_includes/widget-jsdoc/numericRefinementList.md b/docs/_includes/widget-jsdoc/numericRefinementList.md new file mode 100644 index 00000000000..07624531e64 --- /dev/null +++ b/docs/_includes/widget-jsdoc/numericRefinementList.md @@ -0,0 +1,22 @@ +| Param | Description | +| --- | --- | +| `options.container` | CSS Selector or DOMElement to insert the widget | +| `options.attributeName` | Name of the attribute for filtering | +| `options.cssClasses` | CSS classes to add to the wrapping elements: root, list, item | +| `options.cssClasses.root` | CSS class to add to the root element | +| `options.cssClasses.header` | CSS class to add to the header element | +| `options.cssClasses.body` | CSS class to add to the body element | +| `options.cssClasses.footer` | CSS class to add to the footer element | +| `options.cssClasses.list` | CSS class to add to the list element | +| `options.cssClasses.label` | CSS class to add to each link element | +| `options.cssClasses.item` | CSS class to add to each item element | +| `options.cssClasses.radio` | CSS class to add to each radio element (when using the default template) | +| `options.cssClasses.active` | CSS class to add to each active element | +| `options.templates` | Templates to use for the widget | +| `options.templates.header` | Header template | +| `options.templates.item` | Item template, provided with `name`, `count`, `isRefined` | +| `options.templates.footer` | Footer template | +| `options.transformData` | Function to change the object passed to the item template | +| `hideContainerWhenNoResults` | Hide the container when there's no results | + +

* Required

diff --git a/docs/documentation.md b/docs/documentation.md index e378329f47d..ede62067a74 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -532,6 +532,54 @@ results that have either the value `a` or `b` will match.
+#### numericRefinementList + +
+
+{% highlight javascript %} +search.addWidget( + instantsearch.widgets.numericRefinementList({ + container: '#popularity', + attributeName: 'popularity', + options: [ + {name: 'All'}, + {end: 4, name: 'less than 4'}, + {start: 4, end: 4, name: '4'}, + {start: 5, end: 10, name: 'between 5 and 10'}, + {start: 10, name: 'more than 10'} + ], + templates: { + header: 'Price' + }, + cssClasses: { + root: '', + header: '', + body: '', + footer: '', + list: '', + link: '', + active: '' + } + }) +); +{% endhighlight %} +
+ +
+ + +This filtering widget lets the user choose one or multiple values for a single faceted attribute. You can specify if you want to filters to be ORed or ANDed. For example, if you filter on `a` and `b` with `OR`, the +results that have either the value `a` or `b` will match. +{:.description} + +
+ #### toggle
diff --git a/docs/js/instantsearch.js b/docs/js/instantsearch.js new file mode 100644 index 00000000000..94da9277194 --- /dev/null +++ b/docs/js/instantsearch.js @@ -0,0 +1,44950 @@ +/*! instantsearch.js UNRELEASED | © Algolia Inc. and other contributors; Licensed MIT | github.com/algolia/instantsearch.js */(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["instantsearch"] = factory(); + else + root["instantsearch"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + module.exports = __webpack_require__(1); + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + // required for browsers not supporting this + 'use strict'; + + __webpack_require__(2); + + var toFactory = __webpack_require__(3); + + var InstantSearch = __webpack_require__(4); + var instantsearch = toFactory(InstantSearch); + var algoliasearchHelper = __webpack_require__(72); + + instantsearch.widgets = { + hierarchicalMenu: __webpack_require__(216), + hits: __webpack_require__(388), + hitsPerPageSelector: __webpack_require__(391), + indexSelector: __webpack_require__(393), + menu: __webpack_require__(394), + refinementList: __webpack_require__(396), + numericRefinementList: __webpack_require__(398), + pagination: __webpack_require__(400), + priceRanges: __webpack_require__(407), + searchBox: __webpack_require__(412), + rangeSlider: __webpack_require__(414), + stats: __webpack_require__(422), + toggle: __webpack_require__(425) + }; + + instantsearch.version = __webpack_require__(214); + + instantsearch.createQueryString = algoliasearchHelper.url.getQueryStringFromState; + + module.exports = instantsearch; + +/***/ }, +/* 2 */ +/***/ function(module, exports) { + + // https://github.com/es-shims/es5-shim/blob/bf48788c724f255275a801a371c4a3adc304b34c/es5-sham.js#L473 + // Object.freeze is used in various places in our code and is not polyfilled by + // polyfill.io (because not doable): https://github.com/Financial-Times/polyfill-service/issues/232 + // So we "sham" it, which means that the API is here but it just returns the object. + 'use strict'; + + if (!Object.freeze) { + Object.freeze = function freeze(object) { + if (Object(object) !== object) { + throw new TypeError('Object.freeze can only be called on Objects.'); + } + // this is misleading and breaks feature-detection, but + // allows "securable" code to "gracefully" degrade to working + // but insecure code. + return object; + }; + } + +/***/ }, +/* 3 */ +/***/ function(module, exports) { + + "use strict"; + + var _bind = Function.prototype.bind; + function toFactory(Class) { + var Factory = function Factory() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return new (_bind.apply(Class, [null].concat(args)))(); + }; + Factory.__proto__ = Class; + Factory.prototype = Class.prototype; + return Factory; + } + + module.exports = toFactory; + + + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var algoliasearch = __webpack_require__(5); + var algoliasearchHelper = __webpack_require__(72); + + var forEach = __webpack_require__(17); + var merge = __webpack_require__(53); + var union = __webpack_require__(211); + + var EventEmitter = __webpack_require__(63).EventEmitter; + + var urlSyncWidget = __webpack_require__(213); + + function defaultCreateURL() { + return '#'; + } + + var InstantSearch = (function (_EventEmitter) { + _inherits(InstantSearch, _EventEmitter); + + function InstantSearch(_ref) { + var _ref$appId = _ref.appId; + var appId = _ref$appId === undefined ? null : _ref$appId; + var _ref$apiKey = _ref.apiKey; + var apiKey = _ref$apiKey === undefined ? null : _ref$apiKey; + var _ref$indexName = _ref.indexName; + var indexName = _ref$indexName === undefined ? null : _ref$indexName; + var _ref$numberLocale = _ref.numberLocale; + var numberLocale = _ref$numberLocale === undefined ? 'en-EN' : _ref$numberLocale; + var _ref$searchParameters = _ref.searchParameters; + var searchParameters = _ref$searchParameters === undefined ? {} : _ref$searchParameters; + var _ref$urlSync = _ref.urlSync; + var urlSync = _ref$urlSync === undefined ? null : _ref$urlSync; + + _classCallCheck(this, InstantSearch); + + _get(Object.getPrototypeOf(InstantSearch.prototype), 'constructor', this).call(this); + if (appId === null || apiKey === null || indexName === null) { + var usage = '\nUsage: instantsearch({\n appId: \'my_application_id\',\n apiKey: \'my_search_api_key\',\n indexName: \'my_index_name\'\n});'; + throw new Error(usage); + } + + var client = algoliasearch(appId, apiKey); + + this.client = client; + this.helper = null; + this.indexName = indexName; + this.searchParameters = searchParameters || {}; + this.widgets = []; + this.templatesConfig = { + helpers: { + formatNumber: function formatNumber(number, render) { + return Number(render(number)).toLocaleString(numberLocale); + } + }, + compileOptions: {} + }; + this.urlSync = urlSync; + } + + _createClass(InstantSearch, [{ + key: 'addWidget', + value: function addWidget(widgetDefinition) { + // Add the widget to the list of widget + this.widgets.push(widgetDefinition); + } + }, { + key: 'start', + value: function start() { + if (!this.widgets) throw new Error('No widgets were added to instantsearch.js'); + + if (this.urlSync) { + var syncWidget = urlSyncWidget(this.urlSync); + this._createURL = syncWidget.createURL.bind(syncWidget); + this.widgets.push(syncWidget); + } else this._createURL = defaultCreateURL; + + this.searchParameters = this.widgets.reduce(enhanceConfiguration, this.searchParameters); + + var helper = algoliasearchHelper(this.client, this.searchParameters.index || this.indexName, this.searchParameters); + + this.helper = helper; + + this._init(helper.state, helper); + helper.on('result', this._render.bind(this, helper)); + + helper.search(); + } + }, { + key: 'createURL', + value: function createURL(params) { + if (!this._createURL) { + throw new Error('You need to call start() before calling createURL()'); + } + return this._createURL(this.helper.state.setQueryParameters(params)); + } + }, { + key: '_render', + value: function _render(helper, results, state) { + forEach(this.widgets, function (widget) { + if (!widget.render) { + return; + } + widget.render({ + templatesConfig: this.templatesConfig, + results: results, + state: state, + helper: helper, + createURL: this._createURL + }); + }, this); + this.emit('render'); + } + }, { + key: '_init', + value: function _init(state, helper) { + forEach(this.widgets, function (widget) { + if (widget.init) { + widget.init(state, helper, this.templatesConfig); + } + }, this); + } + }]); + + return InstantSearch; + })(EventEmitter); + + function enhanceConfiguration(configuration, widgetDefinition) { + if (!widgetDefinition.getConfiguration) return configuration; + + // Update searchParameters with the configuration from the widgets + var partialConfiguration = widgetDefinition.getConfiguration(configuration); + return merge({}, configuration, partialConfiguration, function (a, b) { + if (Array.isArray(a)) { + return union(a, b); + } + }); + } + + module.exports = InstantSearch; + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + // 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 = algoliasearch; + + var inherits = __webpack_require__(6); + var Promise = window.Promise || __webpack_require__(7).Promise; + + var AlgoliaSearch = __webpack_require__(12); + var errors = __webpack_require__(16); + var inlineHeaders = __webpack_require__(64); + var jsonpRequest = __webpack_require__(68); + + function algoliasearch(applicationID, apiKey, opts) { + var cloneDeep = __webpack_require__(69); + + var getDocumentProtocol = __webpack_require__(70); + + opts = cloneDeep(opts || {}); + + if (opts.protocol === undefined) { + opts.protocol = getDocumentProtocol(); + } + + opts._ua = opts._ua || algoliasearch.ua; + + return new AlgoliaSearchBrowser(applicationID, apiKey, opts); + } + + algoliasearch.version = __webpack_require__(71); + algoliasearch.ua = 'Algolia for vanilla JavaScript ' + algoliasearch.version; + + // we expose into window no matter how we are used, this will allow + // us to easily debug any website running algolia + window.__algolia = { + debug: __webpack_require__(13), + algoliasearch: algoliasearch + }; + + var support = { + hasXMLHttpRequest: 'XMLHttpRequest' in window, + hasXDomainRequest: 'XDomainRequest' in window, + cors: 'withCredentials' in new XMLHttpRequest(), + timeout: 'timeout' 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 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 (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'); + } + + // 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 = function noop() {}; + + req.onload = load; + req.onerror = error; + + if (support.timeout) { + // .timeout supported by both XHR and XDR, + // we do receive timeout event, tested + req.timeout = opts.timeout; + + req.ontimeout = timeout; + } else { + ontimeout = setTimeout(timeout, opts.timeout); + } + + 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()); + } + }); + }; + + 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); + }); + } + }; + + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; + } else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } + } + + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + var require;var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(process, global, module) {/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://github.com/raw/jakearchibald/es6-promise/master/LICENSE + * @version 3.0.2 + */ + + (function() { + "use strict"; + function lib$es6$promise$utils$$objectOrFunction(x) { + return typeof x === 'function' || (typeof x === 'object' && x !== null); + } + + function lib$es6$promise$utils$$isFunction(x) { + return typeof x === 'function'; + } + + function lib$es6$promise$utils$$isMaybeThenable(x) { + return typeof x === 'object' && x !== null; + } + + var lib$es6$promise$utils$$_isArray; + if (!Array.isArray) { + lib$es6$promise$utils$$_isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; + } else { + lib$es6$promise$utils$$_isArray = Array.isArray; + } + + var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray; + var lib$es6$promise$asap$$len = 0; + var lib$es6$promise$asap$$toString = {}.toString; + var lib$es6$promise$asap$$vertxNext; + var lib$es6$promise$asap$$customSchedulerFn; + + var lib$es6$promise$asap$$asap = function asap(callback, arg) { + lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback; + lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg; + lib$es6$promise$asap$$len += 2; + if (lib$es6$promise$asap$$len === 2) { + // If len is 2, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + if (lib$es6$promise$asap$$customSchedulerFn) { + lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush); + } else { + lib$es6$promise$asap$$scheduleFlush(); + } + } + } + + function lib$es6$promise$asap$$setScheduler(scheduleFn) { + lib$es6$promise$asap$$customSchedulerFn = scheduleFn; + } + + function lib$es6$promise$asap$$setAsap(asapFn) { + lib$es6$promise$asap$$asap = asapFn; + } + + var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined; + var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {}; + var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver; + var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; + + // test for web worker but not in IE10 + var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && + typeof importScripts !== 'undefined' && + typeof MessageChannel !== 'undefined'; + + // node + function lib$es6$promise$asap$$useNextTick() { + // node version 0.10.x displays a deprecation warning when nextTick is used recursively + // see https://github.com/cujojs/when/issues/410 for details + return function() { + process.nextTick(lib$es6$promise$asap$$flush); + }; + } + + // vertx + function lib$es6$promise$asap$$useVertxTimer() { + return function() { + lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush); + }; + } + + function lib$es6$promise$asap$$useMutationObserver() { + var iterations = 0; + var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + + return function() { + node.data = (iterations = ++iterations % 2); + }; + } + + // web worker + function lib$es6$promise$asap$$useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = lib$es6$promise$asap$$flush; + return function () { + channel.port2.postMessage(0); + }; + } + + function lib$es6$promise$asap$$useSetTimeout() { + return function() { + setTimeout(lib$es6$promise$asap$$flush, 1); + }; + } + + var lib$es6$promise$asap$$queue = new Array(1000); + function lib$es6$promise$asap$$flush() { + for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) { + var callback = lib$es6$promise$asap$$queue[i]; + var arg = lib$es6$promise$asap$$queue[i+1]; + + callback(arg); + + lib$es6$promise$asap$$queue[i] = undefined; + lib$es6$promise$asap$$queue[i+1] = undefined; + } + + lib$es6$promise$asap$$len = 0; + } + + function lib$es6$promise$asap$$attemptVertx() { + try { + var r = require; + var vertx = __webpack_require__(10); + lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext; + return lib$es6$promise$asap$$useVertxTimer(); + } catch(e) { + return lib$es6$promise$asap$$useSetTimeout(); + } + } + + var lib$es6$promise$asap$$scheduleFlush; + // Decide what async method to use to triggering processing of queued callbacks: + if (lib$es6$promise$asap$$isNode) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick(); + } else if (lib$es6$promise$asap$$BrowserMutationObserver) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver(); + } else if (lib$es6$promise$asap$$isWorker) { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel(); + } else if (lib$es6$promise$asap$$browserWindow === undefined && "function" === 'function') { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx(); + } else { + lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout(); + } + + function lib$es6$promise$$internal$$noop() {} + + var lib$es6$promise$$internal$$PENDING = void 0; + var lib$es6$promise$$internal$$FULFILLED = 1; + var lib$es6$promise$$internal$$REJECTED = 2; + + var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject(); + + function lib$es6$promise$$internal$$selfFulfillment() { + return new TypeError("You cannot resolve a promise with itself"); + } + + function lib$es6$promise$$internal$$cannotReturnOwn() { + return new TypeError('A promises callback cannot return that same promise.'); + } + + function lib$es6$promise$$internal$$getThen(promise) { + try { + return promise.then; + } catch(error) { + lib$es6$promise$$internal$$GET_THEN_ERROR.error = error; + return lib$es6$promise$$internal$$GET_THEN_ERROR; + } + } + + function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch(e) { + return e; + } + } + + function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) { + lib$es6$promise$asap$$asap(function(promise) { + var sealed = false; + var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) { + if (sealed) { return; } + sealed = true; + if (thenable !== value) { + lib$es6$promise$$internal$$resolve(promise, value); + } else { + lib$es6$promise$$internal$$fulfill(promise, value); + } + }, function(reason) { + if (sealed) { return; } + sealed = true; + + lib$es6$promise$$internal$$reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + lib$es6$promise$$internal$$reject(promise, error); + } + }, promise); + } + + function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) { + if (thenable._state === lib$es6$promise$$internal$$FULFILLED) { + lib$es6$promise$$internal$$fulfill(promise, thenable._result); + } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, thenable._result); + } else { + lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) { + lib$es6$promise$$internal$$resolve(promise, value); + }, function(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + }); + } + } + + function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) { + if (maybeThenable.constructor === promise.constructor) { + lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable); + } else { + var then = lib$es6$promise$$internal$$getThen(maybeThenable); + + if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error); + } else if (then === undefined) { + lib$es6$promise$$internal$$fulfill(promise, maybeThenable); + } else if (lib$es6$promise$utils$$isFunction(then)) { + lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then); + } else { + lib$es6$promise$$internal$$fulfill(promise, maybeThenable); + } + } + } + + function lib$es6$promise$$internal$$resolve(promise, value) { + if (promise === value) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment()); + } else if (lib$es6$promise$utils$$objectOrFunction(value)) { + lib$es6$promise$$internal$$handleMaybeThenable(promise, value); + } else { + lib$es6$promise$$internal$$fulfill(promise, value); + } + } + + function lib$es6$promise$$internal$$publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + lib$es6$promise$$internal$$publish(promise); + } + + function lib$es6$promise$$internal$$fulfill(promise, value) { + if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } + + promise._result = value; + promise._state = lib$es6$promise$$internal$$FULFILLED; + + if (promise._subscribers.length !== 0) { + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise); + } + } + + function lib$es6$promise$$internal$$reject(promise, reason) { + if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } + promise._state = lib$es6$promise$$internal$$REJECTED; + promise._result = reason; + + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise); + } + + function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + parent._onerror = null; + + subscribers[length] = child; + subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment; + subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection; + + if (length === 0 && parent._state) { + lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent); + } + } + + function lib$es6$promise$$internal$$publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { return; } + + var child, callback, detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; + } + + function lib$es6$promise$$internal$$ErrorObject() { + this.error = null; + } + + var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject(); + + function lib$es6$promise$$internal$$tryCatch(callback, detail) { + try { + return callback(detail); + } catch(e) { + lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e; + return lib$es6$promise$$internal$$TRY_CATCH_ERROR; + } + } + + function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) { + var hasCallback = lib$es6$promise$utils$$isFunction(callback), + value, error, succeeded, failed; + + if (hasCallback) { + value = lib$es6$promise$$internal$$tryCatch(callback, detail); + + if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn()); + return; + } + + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== lib$es6$promise$$internal$$PENDING) { + // noop + } else if (hasCallback && succeeded) { + lib$es6$promise$$internal$$resolve(promise, value); + } else if (failed) { + lib$es6$promise$$internal$$reject(promise, error); + } else if (settled === lib$es6$promise$$internal$$FULFILLED) { + lib$es6$promise$$internal$$fulfill(promise, value); + } else if (settled === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, value); + } + } + + function lib$es6$promise$$internal$$initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value){ + lib$es6$promise$$internal$$resolve(promise, value); + }, function rejectPromise(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + }); + } catch(e) { + lib$es6$promise$$internal$$reject(promise, e); + } + } + + function lib$es6$promise$enumerator$$Enumerator(Constructor, input) { + var enumerator = this; + + enumerator._instanceConstructor = Constructor; + enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop); + + if (enumerator._validateInput(input)) { + enumerator._input = input; + enumerator.length = input.length; + enumerator._remaining = input.length; + + enumerator._init(); + + if (enumerator.length === 0) { + lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); + } else { + enumerator.length = enumerator.length || 0; + enumerator._enumerate(); + if (enumerator._remaining === 0) { + lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); + } + } + } else { + lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError()); + } + } + + lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) { + return lib$es6$promise$utils$$isArray(input); + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() { + return new Error('Array Methods must be provided an Array'); + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._init = function() { + this._result = new Array(this.length); + }; + + var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator; + + lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() { + var enumerator = this; + + var length = enumerator.length; + var promise = enumerator.promise; + var input = enumerator._input; + + for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { + enumerator._eachEntry(input[i], i); + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { + var enumerator = this; + var c = enumerator._instanceConstructor; + + if (lib$es6$promise$utils$$isMaybeThenable(entry)) { + if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) { + entry._onerror = null; + enumerator._settledAt(entry._state, i, entry._result); + } else { + enumerator._willSettleAt(c.resolve(entry), i); + } + } else { + enumerator._remaining--; + enumerator._result[i] = entry; + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { + var enumerator = this; + var promise = enumerator.promise; + + if (promise._state === lib$es6$promise$$internal$$PENDING) { + enumerator._remaining--; + + if (state === lib$es6$promise$$internal$$REJECTED) { + lib$es6$promise$$internal$$reject(promise, value); + } else { + enumerator._result[i] = value; + } + } + + if (enumerator._remaining === 0) { + lib$es6$promise$$internal$$fulfill(promise, enumerator._result); + } + }; + + lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { + var enumerator = this; + + lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) { + enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value); + }, function(reason) { + enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason); + }); + }; + function lib$es6$promise$promise$all$$all(entries) { + return new lib$es6$promise$enumerator$$default(this, entries).promise; + } + var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all; + function lib$es6$promise$promise$race$$race(entries) { + /*jshint validthis:true */ + var Constructor = this; + + var promise = new Constructor(lib$es6$promise$$internal$$noop); + + if (!lib$es6$promise$utils$$isArray(entries)) { + lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.')); + return promise; + } + + var length = entries.length; + + function onFulfillment(value) { + lib$es6$promise$$internal$$resolve(promise, value); + } + + function onRejection(reason) { + lib$es6$promise$$internal$$reject(promise, reason); + } + + for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { + lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); + } + + return promise; + } + var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race; + function lib$es6$promise$promise$resolve$$resolve(object) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor(lib$es6$promise$$internal$$noop); + lib$es6$promise$$internal$$resolve(promise, object); + return promise; + } + var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve; + function lib$es6$promise$promise$reject$$reject(reason) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor(lib$es6$promise$$internal$$noop); + lib$es6$promise$$internal$$reject(promise, reason); + return promise; + } + var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject; + + var lib$es6$promise$promise$$counter = 0; + + function lib$es6$promise$promise$$needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); + } + + function lib$es6$promise$promise$$needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); + } + + var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise; + /** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise's eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + var promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + Useful for tooling. + @constructor + */ + function lib$es6$promise$promise$$Promise(resolver) { + this._id = lib$es6$promise$promise$$counter++; + this._state = undefined; + this._result = undefined; + this._subscribers = []; + + if (lib$es6$promise$$internal$$noop !== resolver) { + if (!lib$es6$promise$utils$$isFunction(resolver)) { + lib$es6$promise$promise$$needsResolver(); + } + + if (!(this instanceof lib$es6$promise$promise$$Promise)) { + lib$es6$promise$promise$$needsNew(); + } + + lib$es6$promise$$internal$$initializePromise(this, resolver); + } + } + + lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default; + lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default; + lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default; + lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default; + lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler; + lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap; + lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap; + + lib$es6$promise$promise$$Promise.prototype = { + constructor: lib$es6$promise$promise$$Promise, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + var result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + var author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + then: function(onFulfillment, onRejection) { + var parent = this; + var state = parent._state; + + if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) { + return this; + } + + var child = new this.constructor(lib$es6$promise$$internal$$noop); + var result = parent._result; + + if (state) { + var callback = arguments[state - 1]; + lib$es6$promise$asap$$asap(function(){ + lib$es6$promise$$internal$$invokeCallback(state, child, callback, result); + }); + } else { + lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection); + } + + return child; + }, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + 'catch': function(onRejection) { + return this.then(null, onRejection); + } + }; + function lib$es6$promise$polyfill$$polyfill() { + var local; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof self !== 'undefined') { + local = self; + } else { + try { + local = Function('return this')(); + } catch (e) { + throw new Error('polyfill failed because global object is unavailable in this environment'); + } + } + + var P = local.Promise; + + if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) { + return; + } + + local.Promise = lib$es6$promise$promise$$default; + } + var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill; + + var lib$es6$promise$umd$$ES6Promise = { + 'Promise': lib$es6$promise$promise$$default, + 'polyfill': lib$es6$promise$polyfill$$default + }; + + /* global define:true module:true window: true */ + if ("function" === 'function' && __webpack_require__(11)['amd']) { + !(__WEBPACK_AMD_DEFINE_RESULT__ = function() { return lib$es6$promise$umd$$ES6Promise; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else if (typeof module !== 'undefined' && module['exports']) { + module['exports'] = lib$es6$promise$umd$$ES6Promise; + } else if (typeof this !== 'undefined') { + this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise; + } + + lib$es6$promise$polyfill$$default(); + }).call(this); + + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(8), (function() { return this; }()), __webpack_require__(9)(module))) + +/***/ }, +/* 8 */ +/***/ function(module, exports) { + + // shim for using process in browser + + var process = module.exports = {}; + var queue = []; + var draining = false; + var currentQueue; + var queueIndex = -1; + + function cleanUpNextTick() { + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } + } + + function drainQueue() { + if (draining) { + return; + } + var timeout = setTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + clearTimeout(timeout); + } + + process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + setTimeout(drainQueue, 0); + } + }; + + // v8 likes predictible objects + function Item(fun, array) { + this.fun = fun; + this.array = array; + } + Item.prototype.run = function () { + this.fun.apply(null, this.array); + }; + process.title = 'browser'; + process.browser = true; + process.env = {}; + process.argv = []; + process.version = ''; // empty string to avoid regexp issues + process.versions = {}; + + function noop() {} + + process.on = noop; + process.addListener = noop; + process.once = noop; + process.off = noop; + process.removeListener = noop; + process.removeAllListeners = noop; + process.emit = noop; + + process.binding = function (name) { + throw new Error('process.binding is not supported'); + }; + + process.cwd = function () { return '/' }; + process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); + }; + process.umask = function() { return 0; }; + + +/***/ }, +/* 9 */ +/***/ function(module, exports) { + + module.exports = function(module) { + if(!module.webpackPolyfill) { + module.deprecate = function() {}; + module.paths = []; + // module.parent = undefined by default + module.children = []; + module.webpackPolyfill = 1; + } + return module; + } + + +/***/ }, +/* 10 */ +/***/ function(module, exports) { + + /* (ignored) */ + +/***/ }, +/* 11 */ +/***/ function(module, exports) { + + module.exports = function() { throw new Error("define cannot be used indirect"); }; + + +/***/ }, +/* 12 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + module.exports = AlgoliaSearch; + + // default debug activated in dev environments + // this is triggered in package.json, using the envify transform + if (({"NODE_ENV":undefined}).APP_ENV === 'development') { + __webpack_require__(13).enable('algoliasearch*'); + } + + var errors = __webpack_require__(16); + + /* + * 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='http:'] - The protocol used to query Algolia Search API. + * Set to 'https:' to force using https. + * Default to document.location.protocol in browsers + * @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 AlgoliaSearch(applicationID, apiKey, opts) { + var debug = __webpack_require__(13)('algoliasearch'); + + var clone = __webpack_require__(43); + var isArray = __webpack_require__(36); + + var usage = 'Usage: algoliasearch(applicationID, apiKey, opts)'; + + if (!applicationID) { + throw new errors.AlgoliaSearchError('Please provide an application ID. ' + usage); + } + + if (!apiKey) { + throw new errors.AlgoliaSearchError('Please provide an API key. ' + usage); + } + + this.applicationID = applicationID; + this.apiKey = apiKey; + + var defaultHosts = [ + this.applicationID + '-1.algolianet.com', + this.applicationID + '-2.algolianet.com', + this.applicationID + '-3.algolianet.com' + ]; + this.hosts = { + read: [], + write: [] + }; + + this.hostIndex = { + read: 0, + write: 0 + }; + + opts = opts || {}; + + var protocol = opts.protocol || 'https:'; + var timeout = opts.timeout === undefined ? 2000 : opts.timeout; + + // 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 (opts.protocol !== 'http:' && opts.protocol !== 'https:') { + throw new errors.AlgoliaSearchError('protocol must be `http:` or `https:` (was `' + opts.protocol + '`)'); + } + + // no hosts given, add defaults + if (!opts.hosts) { + this.hosts.read = [this.applicationID + '-dsn.algolia.net'].concat(defaultHosts); + this.hosts.write = [this.applicationID + '.algolia.net'].concat(defaultHosts); + } else if (isArray(opts.hosts)) { + 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.requestTimeout = timeout; + + this.extraHeaders = []; + this.cache = {}; + + this._ua = opts._ua; + this._useCache = opts._useCache === undefined ? true : opts._useCache; + + this._setTimeout = opts._setTimeout; + + debug('init done, %j', this); + } + + AlgoliaSearch.prototype = { + /* + * 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 + */ + 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 + */ + 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 callback the result callback called with two arguments + * error: null or Error('message') + * content: the server answer that contains the task ID + */ + copyIndex: function(srcIndexName, dstIndexName, callback) { + var postObj = { + operation: 'copy', destination: dstIndexName + }; + 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 callback the result callback called with two arguments + * error: null or Error('message') + * content: the server answer that contains the task ID + */ + getLogs: function(offset, length, callback) { + if (arguments.length === 0 || typeof offset === 'function') { + // getLogs([cb]) + callback = offset; + offset = 0; + length = 10; + } else if (arguments.length === 1 || typeof length === 'function') { + // getLogs(1, [cb)] + callback = length; + length = 10; + } + + return this._jsonRequest({ + method: 'GET', + url: '/1/logs?offset=' + offset + '&length=' + length, + 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 + */ + 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) + */ + initIndex: function(indexName) { + return new this.Index(this, indexName); + }, + /* + * List all existing user keys with their associated ACLs + * + * @param callback the result callback called with two arguments + * error: null or Error('message') + * content: the server answer with user keys list + */ + listUserKeys: function(callback) { + return this._jsonRequest({ + method: 'GET', + url: '/1/keys', + hostType: 'read', + callback: callback + }); + }, + /* + * Get ACL of a user key + * + * @param key + * @param callback the result callback called with two arguments + * error: null or Error('message') + * content: the server answer with user keys list + */ + getUserKeyACL: function(key, callback) { + return this._jsonRequest({ + method: 'GET', + url: '/1/keys/' + key, + hostType: 'read', + callback: callback + }); + }, + /* + * Delete an existing user key + * @param key + * @param callback the result callback called with two arguments + * error: null or Error('message') + * content: the server answer with user keys list + */ + deleteUserKey: function(key, callback) { + return this._jsonRequest({ + method: 'DELETE', + url: '/1/keys/' + key, + hostType: 'write', + callback: callback + }); + }, + /* + * 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 user keys list + * @return {Promise|undefined} Returns a promise if no callback given + * @example + * client.addUserKey(['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} + */ + addUserKey: function(acls, params, callback) { + 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 + }); + }, + /** + * Add a new global API key + * @deprecated Please use client.addUserKey() + */ + addUserKeyWithValidity: deprecate(function(acls, params, callback) { + return this.addUserKey(acls, params, callback); + }, deprecatedMessage('client.addUserKeyWithValidity()', 'client.addUserKey()')), + + /** + * 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 user keys list + * @return {Promise|undefined} Returns a promise if no callback given + * @example + * client.updateUserKey('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} + */ + updateUserKey: function(key, acls, params, callback) { + 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 + }); + }, + + /** + * Set the extra security tagFilters header + * @param {string|array} tags The list of tags defining the current security filters + */ + 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) + */ + setUserToken: function(userToken) { + this.userToken = userToken; + }, + + /** + * Initialize a new batch of search queries + * @deprecated use client.search() + */ + startQueriesBatch: deprecate(function startQueriesBatchDeprecated() { + this._batch = []; + }, deprecatedMessage('client.startQueriesBatch()', 'client.search()')), + + /** + * Add a search query in the batch + * @deprecated use client.search() + */ + addQueryInBatch: deprecate(function addQueryInBatchDeprecated(indexName, query, args) { + this._batch.push({ + indexName: indexName, + query: query, + params: args + }); + }, deprecatedMessage('client.addQueryInBatch()', 'client.search()')), + + /** + * Clear all queries in client's cache + * @return undefined + */ + clearCache: function() { + this.cache = {}; + }, + + /** + * Launch the batch of queries using XMLHttpRequest. + * @deprecated use client.search() + */ + sendQueriesBatch: deprecate(function sendQueriesBatchDeprecated(callback) { + return this.search(this._batch, callback); + }, deprecatedMessage('client.sendQueriesBatch()', 'client.search()')), + + /** + * Set the number of milliseconds a request can take before automatically being terminated. + * + * @param {Number} milliseconds + */ + setRequestTimeout: function(milliseconds) { + if (milliseconds) { + this.requestTimeout = parseInt(milliseconds, 10); + } + }, + + /** + * 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 + */ + search: function(queries, callback) { + 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) + }; + }) + }; + + return this._jsonRequest({ + cache: this.cache, + method: 'POST', + url: '/1/indexes/*/queries', + body: postObj, + hostType: 'read', + callback: callback + }); + }, + + /** + * 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) + */ + batch: function(operations, callback) { + return this._jsonRequest({ + method: 'POST', + url: '/1/indexes/*/batch', + body: { + requests: operations + }, + hostType: 'write', + callback: callback + }); + }, + + // environment specific methods + destroy: notImplemented, + enableRateLimitForward: notImplemented, + disableRateLimitForward: notImplemented, + useSecuredAPIKey: notImplemented, + disableSecuredAPIKey: notImplemented, + generateSecuredApiKey: notImplemented, + /* + * Index class constructor. + * You should not use this method directly but use initIndex() function + */ + Index: function(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 = {}; + }, + /** + * Add an extra field to the HTTP request + * + * @param name the header field name + * @param value the header field value + */ + setExtraHeader: function(name, value) { + this.extraHeaders.push({ + name: name.toLowerCase(), value: value + }); + }, + + /** + * 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 + */ + addAlgoliaAgent: function(algoliaAgent) { + this._ua += ';' + algoliaAgent; + }, + + _sendQueriesBatch: function(params, callback) { + function prepareParams() { + var reqParams = ''; + for (var i = 0; i < params.requests.length; ++i) { + var q = '/1/indexes/' + + encodeURIComponent(params.requests[i].indexName) + + '?' + params.requests[i].params; + reqParams += i + '=' + encodeURIComponent(q) + '&'; + } + return reqParams; + } + + return this._jsonRequest({ + cache: this.cache, + method: 'POST', + url: '/1/indexes/*/queries', + body: params, + hostType: 'read', + fallback: { + method: 'GET', + url: '/1/indexes/*', + body: { + params: prepareParams() + } + }, + callback: callback + }); + }, + /* + * Wrapper that try all hosts to maximize the quality of service + */ + _jsonRequest: function(opts) { + var requestDebug = __webpack_require__(13)('algoliasearch:' + opts.url); + + var body; + var cache = opts.cache; + var client = this; + var tries = 0; + var usingFallback = false; + + if (opts.body !== undefined) { + body = safeJSONStringify(opts.body); + } + + requestDebug('request start'); + + function doRequest(requester, reqOpts) { + var cacheID; + + if (client._useCache) { + cacheID = opts.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 && body) { + cacheID += '_body_' + reqOpts.body; + } + + // handle cache existence + if (client._useCache && cache && cache[cacheID] !== undefined) { + requestDebug('serving response from cache'); + return client._promise.resolve(JSON.parse(cache[cacheID])); + } + + // if we reached max tries + if (tries >= client.hosts[opts.hostType].length || + // or we need to switch to fallback + client.useFallback && !usingFallback) { + // and there's no fallback or we are already using a fallback + if (!opts.fallback || !client._request.fallback || 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 + )); + } + + requestDebug('switching to fallback'); + + // let's try the fallback starting from here + tries = 0; + + // method, url and body are fallback dependent + reqOpts.method = opts.fallback.method; + reqOpts.url = opts.fallback.url; + reqOpts.jsonBody = opts.fallback.body; + if (reqOpts.jsonBody) { + reqOpts.body = safeJSONStringify(reqOpts.jsonBody); + } + + reqOpts.timeout = client.requestTimeout * (tries + 1); + client.hostIndex[opts.hostType] = 0; + usingFallback = true; // the current request is now using fallback + return doRequest(client._request.fallback, reqOpts); + } + + var url = client.hosts[opts.hostType][client.hostIndex[opts.hostType]] + reqOpts.url; + var options = { + body: body, + jsonBody: opts.body, + method: reqOpts.method, + headers: client._computeRequestHeaders(), + timeout: reqOpts.timeout, + debug: requestDebug + }; + + requestDebug('method: %s, url: %s, headers: %j, timeout: %d', + options.method, url, options.headers, options.timeout); + + 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); + + if (({"NODE_ENV":undefined}).DEBUG && ({"NODE_ENV":undefined}).DEBUG.indexOf('debugBody') !== -1) { + requestDebug('body: %j', httpResponse.body); + } + + var ok = status === 200 || status === 201; + var retry = !ok && Math.floor(status / 100) !== 4 && Math.floor(status / 100) !== 1; + + if (client._useCache && ok && cache) { + cache[cacheID] = httpResponse.responseText; + } + + if (ok) { + return httpResponse.body; + } + + if (retry) { + tries += 1; + return retryRequest(); + } + + var unrecoverableError = new errors.AlgoliaSearchError( + httpResponse.body && httpResponse.body.message + ); + + 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); + + 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[opts.hostType].length && + (usingFallback || !opts.fallback || !client._request.fallback)) { + // stop request implementation for this command + return client._promise.reject(err); + } + + client.hostIndex[opts.hostType] = ++client.hostIndex[opts.hostType] % client.hosts[opts.hostType].length; + + if (err instanceof errors.RequestTimeout) { + return retryRequest(); + } else if (client._request.fallback && !client.useFallback) { + // if any error occured but timeout, use fallback for the rest + // of the session + client.useFallback = true; + } + + return doRequest(requester, reqOpts); + } + + function retryRequest() { + client.hostIndex[opts.hostType] = ++client.hostIndex[opts.hostType] % client.hosts[opts.hostType].length; + reqOpts.timeout = client.requestTimeout * (tries + 1); + return doRequest(requester, reqOpts); + } + } + + // we can use a fallback if forced AND fallback parameters are available + var useFallback = client.useFallback && opts.fallback; + var requestOptions = useFallback ? opts.fallback : opts; + + var promise = doRequest( + // set the requester + useFallback ? client._request.fallback : client._request, { + url: requestOptions.url, + method: requestOptions.method, + body: body, + jsonBody: opts.body, + timeout: client.requestTimeout * (tries + 1) + } + ); + + // either we have a callback + // either we are using promises + if (opts.callback) { + promise.then(function okCb(content) { + exitPromise(function() { + opts.callback(null, content); + }, client._setTimeout || setTimeout); + }, function nookCb(err) { + exitPromise(function() { + opts.callback(err); + }, client._setTimeout || setTimeout); + }); + } else { + return promise; + } + }, + + /* + * Transform search param object in query string + */ + _getSearchParams: function(args, params) { + if (this._isUndefined(args) || 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; + }, + + _isUndefined: function(obj) { + return obj === void 0; + }, + + _computeRequestHeaders: function() { + var forEach = __webpack_require__(17); + + var requestHeaders = { + 'x-algolia-api-key': this.apiKey, + 'x-algolia-application-id': this.applicationID, + 'x-algolia-agent': this._ua + }; + + if (this.userToken) { + requestHeaders['x-algolia-usertoken'] = this.userToken; + } + + if (this.securityTags) { + requestHeaders['x-algolia-tagfilters'] = this.securityTags; + } + + if (this.extraHeaders) { + forEach(this.extraHeaders, function addToRequestHeaders(header) { + requestHeaders[header.name] = header.value; + }); + } + + return requestHeaders; + } + }; + + /* + * Contains all the functions related to one index + * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object + */ + AlgoliaSearch.prototype.Index.prototype = { + /* + * Clear all queries in cache + */ + clearCache: function() { + this.cache = {}; + }, + /* + * 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 + */ + 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 + */ + addObjects: function(objects, callback) { + 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 + }); + }, + /* + * 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 + */ + 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 + */ + getObjects: function(objectIDs, attributesToRetrieve, callback) { + 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 + }); + }, + + /* + * 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 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 + */ + partialUpdateObject: function(partialObject, callback) { + var indexObj = this; + return this.as._jsonRequest({ + method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial', + 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 + */ + partialUpdateObjects: function(objects, callback) { + var indexObj = this; + var postObj = { + requests: [] + }; + for (var i = 0; i < objects.length; ++i) { + var request = { + action: 'partialUpdateObject', + 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 + */ + 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 + */ + saveObjects: function(objects, callback) { + 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 + */ + deleteObject: function(objectID, callback) { + if (typeof objectID === 'function' || typeof objectID !== 'string' && typeof objectID !== 'number') { + var err = new errors.AlgoliaSearchError('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 + */ + deleteObjects: function(objectIDs, callback) { + 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') + */ + deleteByQuery: function(query, params, callback) { + var clone = __webpack_require__(43); + + 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); + } + }, + + /* + * Search inside the index using XMLHttpRequest request (Using a POST query to + * minimize number of OPTIONS queries: Cross-Origin Resource Sharing). + * + * @param query the full text query + * @param 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 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. + */ + 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 query the similar query + * @param 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 + */ + 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} + */ + // pre 3.5.0 usage, backward compatible + // browse: function(page, hitsPerPage, callback) { + browse: function(query, queryParameters, callback) { + var merge = __webpack_require__(53); + + 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: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse?' + 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} + */ + browseFrom: function(cursor, callback) { + return this.as._jsonRequest({ + method: 'GET', + url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/browse?cursor=' + encodeURIComponent(cursor), + hostType: 'read', + 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} + */ + browseAll: function(query, queryParameters) { + if (typeof query === 'object') { + queryParameters = query; + query = undefined; + } + + var merge = __webpack_require__(53); + + var IndexBrowser = __webpack_require__(62); + + 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 queryString; + + if (cursor !== undefined) { + queryString = 'cursor=' + encodeURIComponent(cursor); + } else { + queryString = params; + } + + client._jsonRequest({ + method: 'GET', + url: '/1/indexes/' + encodeURIComponent(index.indexName) + '/browse?' + queryString, + hostType: 'read', + 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) + */ + ttAdapter: 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); + }); + }; + }, + + /* + * 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 + */ + 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 + */ + 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 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 + */ + getSettings: function(callback) { + var indexObj = this; + return this.as._jsonRequest({ + method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', + hostType: 'read', + 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 ""). + * - highlightPostTag: (string) Specify the string that is inserted after + * the highlighted parts in the query result (default to ""). + * - 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 + */ + setSettings: function(settings, callback) { + var indexObj = this; + return this.as._jsonRequest({ + method: 'PUT', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', + hostType: 'write', + body: settings, + callback: callback + }); + }, + /* + * List all existing user keys associated to this index + * + * @param callback the result callback called with two arguments + * error: null or Error('message') + * content: the server answer with user keys list + */ + listUserKeys: function(callback) { + var indexObj = this; + return this.as._jsonRequest({ + method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', + hostType: 'read', + callback: callback + }); + }, + /* + * Get ACL of a user 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 user keys list + */ + getUserKeyACL: function(key, callback) { + var indexObj = this; + return this.as._jsonRequest({ + method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, + hostType: 'read', + callback: callback + }); + }, + /* + * Delete an existing user 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 user keys list + */ + deleteUserKey: function(key, callback) { + var indexObj = this; + return this.as._jsonRequest({ + method: 'DELETE', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, + hostType: 'write', + callback: callback + }); + }, + /* + * 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 user keys list + * @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} + */ + addUserKey: function(acls, params, callback) { + 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 + }); + }, + + /** + * Add an existing user key associated to this index + * @deprecated use index.addUserKey() + */ + addUserKeyWithValidity: deprecate(function deprecatedAddUserKeyWithValidity(acls, params, callback) { + return this.addUserKey(acls, params, callback); + }, deprecatedMessage('index.addUserKeyWithValidity()', 'index.addUserKey()')), + + /** + * 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.updateUserKey('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} + */ + updateUserKey: function(key, acls, params, callback) { + 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 + }); + }, + + _search: function(params, callback) { + return this.as._jsonRequest({ + cache: this.cache, + method: 'POST', + 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 + }); + }, + + as: null, + indexName: null, + typeAheadArgs: null, + typeAheadValueOption: null + }; + + // extracted from https://github.com/component/map/blob/master/index.js + // without the crazy toFunction thing + function map(arr, fn) { + var ret = []; + for (var i = 0; i < arr.length; ++i) { + ret.push(fn(arr[i], i)); + } + return ret; + } + + function prepareHost(protocol) { + return function prepare(host) { + return protocol + '//' + host.toLowerCase(); + }; + } + + 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); + } + + function deprecatedMessage(previousUsage, newUsage) { + var githubAnchorLink = previousUsage.toLowerCase() + .replace('.', '') + .replace('()', ''); + + return 'algoliasearch: `' + previousUsage + '` was replaced by `' + newUsage + + '`. Please see https://github.com/algolia/algoliasearch-client-js/wiki/Deprecated#' + githubAnchorLink; + } + + // 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 + function exitPromise(fn, _setTimeout) { + _setTimeout(fn, 0); + } + + function deprecate(fn, message) { + var warned = false; + + function deprecated() { + if (!warned) { + /* eslint no-console:0 */ + console.log(message); + warned = true; + } + + return fn.apply(this, arguments); + } + + return deprecated; + } + + // 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 buildSearchMethod(queryParam) { + 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)'); + } + + if (arguments.length === 0 || typeof query === 'function') { + // .search(), .search(cb) + callback = query; + query = ''; + } else if (arguments.length === 1 || typeof args === 'function') { + // .search(query/args), .search(query, cb) + callback = args; + args = undefined; + } + + // .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); + } + + if (args !== undefined) { + // `_getSearchParams` will augment params, do not be fooled by the = versus += from previous if + params = this.as._getSearchParams(args, params); + } + + return this._search(params, callback); + }; + } + + +/***/ }, +/* 13 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + + exports = module.exports = __webpack_require__(14); + exports.log = log; + exports.formatArgs = formatArgs; + exports.save = save; + exports.load = load; + exports.useColors = useColors; + exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + + /** + * Colors. + */ + + exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' + ]; + + /** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + + function useColors() { + // is webkit? http://stackoverflow.com/a/16459606/376773 + return ('WebkitAppearance' in document.documentElement.style) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (window.console && (console.firebug || (console.exception && console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); + } + + /** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + + exports.formatters.j = function(v) { + return JSON.stringify(v); + }; + + + /** + * Colorize log arguments if enabled. + * + * @api public + */ + + function formatArgs() { + var args = arguments; + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return args; + + var c = 'color: ' + this.color; + args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); + return args; + } + + /** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + + function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); + } + + /** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + + function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} + } + + /** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + + function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + return r; + } + + /** + * Enable namespaces listed in `localStorage.debug` initially. + */ + + exports.enable(load()); + + /** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + + function localstorage(){ + try { + return window.localStorage; + } catch (e) {} + } + + +/***/ }, +/* 14 */ +/***/ function(module, exports, __webpack_require__) { + + + /** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + + exports = module.exports = debug; + exports.coerce = coerce; + exports.disable = disable; + exports.enable = enable; + exports.enabled = enabled; + exports.humanize = __webpack_require__(15); + + /** + * The currently active debug mode names, and names to skip. + */ + + exports.names = []; + exports.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lowercased letter, i.e. "n". + */ + + exports.formatters = {}; + + /** + * Previously assigned color. + */ + + var prevColor = 0; + + /** + * Previous log timestamp. + */ + + var prevTime; + + /** + * Select a color. + * + * @return {Number} + * @api private + */ + + function selectColor() { + return exports.colors[prevColor++ % exports.colors.length]; + } + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + + function debug(namespace) { + + // define the `disabled` version + function disabled() { + } + disabled.enabled = false; + + // define the `enabled` version + function enabled() { + + var self = enabled; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // add the `color` if not set + if (null == self.useColors) self.useColors = exports.useColors(); + if (null == self.color && self.useColors) self.color = selectColor(); + + var args = Array.prototype.slice.call(arguments); + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %o + args = ['%o'].concat(args); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + if ('function' === typeof exports.formatArgs) { + args = exports.formatArgs.apply(self, args); + } + var logFn = enabled.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + enabled.enabled = true; + + var fn = exports.enabled(namespace) ? enabled : disabled; + + fn.namespace = namespace; + + return fn; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + + function enable(namespaces) { + exports.save(namespaces); + + var split = (namespaces || '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } + } + + /** + * Disable debug output. + * + * @api public + */ + + function disable() { + exports.enable(''); + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + + function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + + function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; + } + + +/***/ }, +/* 15 */ +/***/ function(module, exports) { + + /** + * Helpers. + */ + + var s = 1000; + var m = s * 60; + var h = m * 60; + var d = h * 24; + var y = d * 365.25; + + /** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} options + * @return {String|Number} + * @api public + */ + + module.exports = function(val, options){ + options = options || {}; + if ('string' == typeof val) return parse(val); + // long, short were "future reserved words in js", YUI compressor fail on them + // https://github.com/algolia/algoliasearch-client-js/issues/113#issuecomment-111978606 + // https://github.com/yui/yuicompressor/issues/47 + // https://github.com/rauchg/ms.js/pull/40 + return options['long'] + ? _long(val) + : _short(val); + }; + + /** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + + function parse(str) { + str = '' + str; + if (str.length > 10000) return; + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str); + if (!match) return; + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + } + } + + /** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function _short(ms) { + if (ms >= d) return Math.round(ms / d) + 'd'; + if (ms >= h) return Math.round(ms / h) + 'h'; + if (ms >= m) return Math.round(ms / m) + 'm'; + if (ms >= s) return Math.round(ms / s) + 's'; + return ms + 'ms'; + } + + /** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function _long(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; + } + + /** + * Pluralization helper. + */ + + function plural(ms, n, name) { + if (ms < n) return; + if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; + return Math.ceil(ms / n) + ' ' + name + 's'; + } + + +/***/ }, +/* 16 */ +/***/ function(module, exports, __webpack_require__) { + + '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 = __webpack_require__(6); + + function AlgoliaSearchError(message, extraProperties) { + var forEach = __webpack_require__(17); + + 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 = this.constructor.name; + 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', + '