'use strict'; var global = require('global'); var Promise = global.Promise || require('es6-promise').Promise; // This is the standalone browser build entry point // Browser implementation of the Algolia Search JavaScript client, // using XMLHttpRequest, XDomainRequest and JSONP as fallback module.exports = function createAlgoliasearch(AlgoliaSearch, uaSuffix) { var inherits = require('inherits'); var errors = require('../errors'); var inlineHeaders = require('./inline-headers'); var jsonpRequest = require('./jsonp-request'); var places = require('../places.js'); uaSuffix = uaSuffix || ''; if (process.env.NODE_ENV === 'debug') { require('debug').enable('algoliasearch*'); } function algoliasearch(applicationID, apiKey, opts) { var cloneDeep = require('../clone.js'); opts = cloneDeep(opts || {}); opts._ua = opts._ua || algoliasearch.ua; return new AlgoliaSearchBrowser(applicationID, apiKey, opts); } algoliasearch.version = require('../version.js'); algoliasearch.ua = 'Algolia for vanilla JavaScript ' + uaSuffix + algoliasearch.version; algoliasearch.initPlaces = places(algoliasearch); // we expose into window no matter how we are used, this will allow // us to easily debug any website running algolia global.__algolia = { debug: require('debug'), algoliasearch: algoliasearch }; var support = { hasXMLHttpRequest: 'XMLHttpRequest' in global, hasXDomainRequest: 'XDomainRequest' in global }; if (support.hasXMLHttpRequest) { support.cors = 'withCredentials' in new XMLHttpRequest(); } function AlgoliaSearchBrowser() { // call AlgoliaSearch constructor AlgoliaSearch.apply(this, arguments); } inherits(AlgoliaSearchBrowser, AlgoliaSearch); AlgoliaSearchBrowser.prototype._request = function request(url, opts) { return new Promise(function wrapRequest(resolve, reject) { // no cors or XDomainRequest, no request if (!support.cors && !support.hasXDomainRequest) { // very old browser, not supported reject(new errors.Network('CORS not supported')); return; } url = inlineHeaders(url, opts.headers); var body = opts.body; var req = support.cors ? new XMLHttpRequest() : new XDomainRequest(); var reqTimeout; var timedOut; var connected = false; reqTimeout = setTimeout(onTimeout, opts.timeouts.connect); // we set an empty onprogress listener // so that XDomainRequest on IE9 is not aborted // refs: // - https://github.com/algolia/algoliasearch-client-js/issues/76 // - https://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified?forum=iewebdevelopment req.onprogress = onProgress; if ('onreadystatechange' in req) req.onreadystatechange = onReadyStateChange; req.onload = onLoad; req.onerror = onError; // do not rely on default XHR async flag, as some analytics code like hotjar // breaks it and set it to false by default if (req instanceof XMLHttpRequest) { req.open(opts.method, url, true); // The Analytics API never accepts Auth headers as query string // this option exists specifically for them. if (opts.forceAuthHeaders) { req.setRequestHeader( 'x-algolia-application-id', opts.headers['x-algolia-application-id'] ); req.setRequestHeader( 'x-algolia-api-key', opts.headers['x-algolia-api-key'] ); } } else { req.open(opts.method, url); } // headers are meant to be sent after open if (support.cors) { if (body) { if (opts.method === 'POST') { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Simple_requests req.setRequestHeader('content-type', 'application/x-www-form-urlencoded'); } else { req.setRequestHeader('content-type', 'application/json'); } } req.setRequestHeader('accept', 'application/json'); } if (body) { req.send(body); } else { req.send(); } // event object not received in IE8, at least // but we do not use it, still important to note function onLoad(/* event */) { // When browser does not supports req.timeout, we can // have both a load and timeout event, since handled by a dumb setTimeout if (timedOut) { return; } clearTimeout(reqTimeout); var out; try { out = { body: JSON.parse(req.responseText), responseText: req.responseText, statusCode: req.status, // XDomainRequest does not have any response headers headers: req.getAllResponseHeaders && req.getAllResponseHeaders() || {} }; } catch (e) { out = new errors.UnparsableJSON({ more: req.responseText }); } if (out instanceof errors.UnparsableJSON) { reject(out); } else { resolve(out); } } function onError(event) { if (timedOut) { return; } clearTimeout(reqTimeout); // error event is trigerred both with XDR/XHR on: // - DNS error // - unallowed cross domain request reject( new errors.Network({ more: event }) ); } function onTimeout() { timedOut = true; req.abort(); reject(new errors.RequestTimeout()); } function onConnect() { connected = true; clearTimeout(reqTimeout); reqTimeout = setTimeout(onTimeout, opts.timeouts.complete); } function onProgress() { if (!connected) onConnect(); } function onReadyStateChange() { if (!connected && req.readyState > 1) onConnect(); } }); }; AlgoliaSearchBrowser.prototype._request.fallback = function requestFallback(url, opts) { url = inlineHeaders(url, opts.headers); return new Promise(function wrapJsonpRequest(resolve, reject) { jsonpRequest(url, opts, function jsonpRequestDone(err, content) { if (err) { reject(err); return; } resolve(content); }); }); }; AlgoliaSearchBrowser.prototype._promise = { reject: function rejectPromise(val) { return Promise.reject(val); }, resolve: function resolvePromise(val) { return Promise.resolve(val); }, delay: function delayPromise(ms) { return new Promise(function resolveOnTimeout(resolve/* , reject*/) { setTimeout(resolve, ms); }); }, all: function all(promises) { return Promise.all(promises); } }; return algoliasearch; };