123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915 |
- function createBrowserLocalStorageCache(options) {
- const namespaceKey = `algoliasearch-client-js-${options.key}`;
- // eslint-disable-next-line functional/no-let
- let storage;
- const getStorage = () => {
- if (storage === undefined) {
- storage = options.localStorage || window.localStorage;
- }
- return storage;
- };
- const getNamespace = () => {
- return JSON.parse(getStorage().getItem(namespaceKey) || '{}');
- };
- return {
- get(key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- return Promise.resolve()
- .then(() => {
- const keyAsString = JSON.stringify(key);
- const value = getNamespace()[keyAsString];
- return Promise.all([value || defaultValue(), value !== undefined]);
- })
- .then(([value, exists]) => {
- return Promise.all([value, exists || events.miss(value)]);
- })
- .then(([value]) => value);
- },
- set(key, value) {
- return Promise.resolve().then(() => {
- const namespace = getNamespace();
- // eslint-disable-next-line functional/immutable-data
- namespace[JSON.stringify(key)] = value;
- getStorage().setItem(namespaceKey, JSON.stringify(namespace));
- return value;
- });
- },
- delete(key) {
- return Promise.resolve().then(() => {
- const namespace = getNamespace();
- // eslint-disable-next-line functional/immutable-data
- delete namespace[JSON.stringify(key)];
- getStorage().setItem(namespaceKey, JSON.stringify(namespace));
- });
- },
- clear() {
- return Promise.resolve().then(() => {
- getStorage().removeItem(namespaceKey);
- });
- },
- };
- }
- // @todo Add logger on options to debug when caches go wrong.
- function createFallbackableCache(options) {
- const caches = [...options.caches];
- const current = caches.shift(); // eslint-disable-line functional/immutable-data
- if (current === undefined) {
- return createNullCache();
- }
- return {
- get(key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- return current.get(key, defaultValue, events).catch(() => {
- return createFallbackableCache({ caches }).get(key, defaultValue, events);
- });
- },
- set(key, value) {
- return current.set(key, value).catch(() => {
- return createFallbackableCache({ caches }).set(key, value);
- });
- },
- delete(key) {
- return current.delete(key).catch(() => {
- return createFallbackableCache({ caches }).delete(key);
- });
- },
- clear() {
- return current.clear().catch(() => {
- return createFallbackableCache({ caches }).clear();
- });
- },
- };
- }
- function createNullCache() {
- return {
- get(_key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- const value = defaultValue();
- return value
- .then(result => Promise.all([result, events.miss(result)]))
- .then(([result]) => result);
- },
- set(_key, value) {
- return Promise.resolve(value);
- },
- delete(_key) {
- return Promise.resolve();
- },
- clear() {
- return Promise.resolve();
- },
- };
- }
- function createInMemoryCache(options = { serializable: true }) {
- // eslint-disable-next-line functional/no-let
- let cache = {};
- return {
- get(key, defaultValue, events = {
- miss: () => Promise.resolve(),
- }) {
- const keyAsString = JSON.stringify(key);
- if (keyAsString in cache) {
- return Promise.resolve(options.serializable ? JSON.parse(cache[keyAsString]) : cache[keyAsString]);
- }
- const promise = defaultValue();
- const miss = (events && events.miss) || (() => Promise.resolve());
- return promise.then((value) => miss(value)).then(() => promise);
- },
- set(key, value) {
- // eslint-disable-next-line functional/immutable-data
- cache[JSON.stringify(key)] = options.serializable ? JSON.stringify(value) : value;
- return Promise.resolve(value);
- },
- delete(key) {
- // eslint-disable-next-line functional/immutable-data
- delete cache[JSON.stringify(key)];
- return Promise.resolve();
- },
- clear() {
- cache = {};
- return Promise.resolve();
- },
- };
- }
- function createAuth(authMode, appId, apiKey) {
- const credentials = {
- 'x-algolia-api-key': apiKey,
- 'x-algolia-application-id': appId,
- };
- return {
- headers() {
- return authMode === AuthMode.WithinHeaders ? credentials : {};
- },
- queryParameters() {
- return authMode === AuthMode.WithinQueryParameters ? credentials : {};
- },
- };
- }
- // eslint-disable-next-line functional/prefer-readonly-type
- function shuffle(array) {
- let c = array.length - 1; // eslint-disable-line functional/no-let
- // eslint-disable-next-line functional/no-loop-statement
- for (c; c > 0; c--) {
- const b = Math.floor(Math.random() * (c + 1));
- const a = array[c];
- array[c] = array[b]; // eslint-disable-line functional/immutable-data, no-param-reassign
- array[b] = a; // eslint-disable-line functional/immutable-data, no-param-reassign
- }
- return array;
- }
- function addMethods(base, methods) {
- if (!methods) {
- return base;
- }
- Object.keys(methods).forEach(key => {
- // eslint-disable-next-line functional/immutable-data, no-param-reassign
- base[key] = methods[key](base);
- });
- return base;
- }
- function encode(format, ...args) {
- // eslint-disable-next-line functional/no-let
- let i = 0;
- return format.replace(/%s/g, () => encodeURIComponent(args[i++]));
- }
- const version = '4.13.0';
- const AuthMode = {
- /**
- * If auth credentials should be in query parameters.
- */
- WithinQueryParameters: 0,
- /**
- * If auth credentials should be in headers.
- */
- WithinHeaders: 1,
- };
- function createMappedRequestOptions(requestOptions, timeout) {
- const options = requestOptions || {};
- const data = options.data || {};
- Object.keys(options).forEach(key => {
- if (['timeout', 'headers', 'queryParameters', 'data', 'cacheable'].indexOf(key) === -1) {
- data[key] = options[key]; // eslint-disable-line functional/immutable-data
- }
- });
- return {
- data: Object.entries(data).length > 0 ? data : undefined,
- timeout: options.timeout || timeout,
- headers: options.headers || {},
- queryParameters: options.queryParameters || {},
- cacheable: options.cacheable,
- };
- }
- const CallEnum = {
- /**
- * If the host is read only.
- */
- Read: 1,
- /**
- * If the host is write only.
- */
- Write: 2,
- /**
- * If the host is both read and write.
- */
- Any: 3,
- };
- const HostStatusEnum = {
- Up: 1,
- Down: 2,
- Timeouted: 3,
- };
- // By default, API Clients at Algolia have expiration delay
- // of 5 mins. In the JavaScript client, we have 2 mins.
- const EXPIRATION_DELAY = 2 * 60 * 1000;
- function createStatefulHost(host, status = HostStatusEnum.Up) {
- return {
- ...host,
- status,
- lastUpdate: Date.now(),
- };
- }
- function isStatefulHostUp(host) {
- return host.status === HostStatusEnum.Up || Date.now() - host.lastUpdate > EXPIRATION_DELAY;
- }
- function isStatefulHostTimeouted(host) {
- return (host.status === HostStatusEnum.Timeouted && Date.now() - host.lastUpdate <= EXPIRATION_DELAY);
- }
- function createStatelessHost(options) {
- if (typeof options === 'string') {
- return {
- protocol: 'https',
- url: options,
- accept: CallEnum.Any,
- };
- }
- return {
- protocol: options.protocol || 'https',
- url: options.url,
- accept: options.accept || CallEnum.Any,
- };
- }
- const MethodEnum = {
- Delete: 'DELETE',
- Get: 'GET',
- Post: 'POST',
- Put: 'PUT',
- };
- function createRetryableOptions(hostsCache, statelessHosts) {
- return Promise.all(statelessHosts.map(statelessHost => {
- return hostsCache.get(statelessHost, () => {
- return Promise.resolve(createStatefulHost(statelessHost));
- });
- })).then(statefulHosts => {
- const hostsUp = statefulHosts.filter(host => isStatefulHostUp(host));
- const hostsTimeouted = statefulHosts.filter(host => isStatefulHostTimeouted(host));
- /**
- * Note, we put the hosts that previously timeouted on the end of the list.
- */
- const hostsAvailable = [...hostsUp, ...hostsTimeouted];
- const statelessHostsAvailable = hostsAvailable.length > 0
- ? hostsAvailable.map(host => createStatelessHost(host))
- : statelessHosts;
- return {
- getTimeout(timeoutsCount, baseTimeout) {
- /**
- * Imagine that you have 4 hosts, if timeouts will increase
- * on the following way: 1 (timeouted) > 4 (timeouted) > 5 (200)
- *
- * Note that, the very next request, we start from the previous timeout
- *
- * 5 (timeouted) > 6 (timeouted) > 7 ...
- *
- * This strategy may need to be reviewed, but is the strategy on the our
- * current v3 version.
- */
- const timeoutMultiplier = hostsTimeouted.length === 0 && timeoutsCount === 0
- ? 1
- : hostsTimeouted.length + 3 + timeoutsCount;
- return timeoutMultiplier * baseTimeout;
- },
- statelessHosts: statelessHostsAvailable,
- };
- });
- }
- const isNetworkError = ({ isTimedOut, status }) => {
- return !isTimedOut && ~~status === 0;
- };
- const isRetryable = (response) => {
- const status = response.status;
- const isTimedOut = response.isTimedOut;
- return (isTimedOut || isNetworkError(response) || (~~(status / 100) !== 2 && ~~(status / 100) !== 4));
- };
- const isSuccess = ({ status }) => {
- return ~~(status / 100) === 2;
- };
- const retryDecision = (response, outcomes) => {
- if (isRetryable(response)) {
- return outcomes.onRetry(response);
- }
- if (isSuccess(response)) {
- return outcomes.onSuccess(response);
- }
- return outcomes.onFail(response);
- };
- function retryableRequest(transporter, statelessHosts, request, requestOptions) {
- const stackTrace = []; // eslint-disable-line functional/prefer-readonly-type
- /**
- * First we prepare the payload that do not depend from hosts.
- */
- const data = serializeData(request, requestOptions);
- const headers = serializeHeaders(transporter, requestOptions);
- const method = request.method;
- // On `GET`, the data is proxied to query parameters.
- const dataQueryParameters = request.method !== MethodEnum.Get
- ? {}
- : {
- ...request.data,
- ...requestOptions.data,
- };
- const queryParameters = {
- 'x-algolia-agent': transporter.userAgent.value,
- ...transporter.queryParameters,
- ...dataQueryParameters,
- ...requestOptions.queryParameters,
- };
- let timeoutsCount = 0; // eslint-disable-line functional/no-let
- const retry = (hosts, // eslint-disable-line functional/prefer-readonly-type
- getTimeout) => {
- /**
- * We iterate on each host, until there is no host left.
- */
- const host = hosts.pop(); // eslint-disable-line functional/immutable-data
- if (host === undefined) {
- throw createRetryError(stackTraceWithoutCredentials(stackTrace));
- }
- const payload = {
- data,
- headers,
- method,
- url: serializeUrl(host, request.path, queryParameters),
- connectTimeout: getTimeout(timeoutsCount, transporter.timeouts.connect),
- responseTimeout: getTimeout(timeoutsCount, requestOptions.timeout),
- };
- /**
- * The stackFrame is pushed to the stackTrace so we
- * can have information about onRetry and onFailure
- * decisions.
- */
- const pushToStackTrace = (response) => {
- const stackFrame = {
- request: payload,
- response,
- host,
- triesLeft: hosts.length,
- };
- // eslint-disable-next-line functional/immutable-data
- stackTrace.push(stackFrame);
- return stackFrame;
- };
- const decisions = {
- onSuccess: response => deserializeSuccess(response),
- onRetry(response) {
- const stackFrame = pushToStackTrace(response);
- /**
- * If response is a timeout, we increaset the number of
- * timeouts so we can increase the timeout later.
- */
- if (response.isTimedOut) {
- timeoutsCount++;
- }
- return Promise.all([
- /**
- * Failures are individually send the logger, allowing
- * the end user to debug / store stack frames even
- * when a retry error does not happen.
- */
- transporter.logger.info('Retryable failure', stackFrameWithoutCredentials(stackFrame)),
- /**
- * We also store the state of the host in failure cases. If the host, is
- * down it will remain down for the next 2 minutes. In a timeout situation,
- * this host will be added end of the list of hosts on the next request.
- */
- transporter.hostsCache.set(host, createStatefulHost(host, response.isTimedOut ? HostStatusEnum.Timeouted : HostStatusEnum.Down)),
- ]).then(() => retry(hosts, getTimeout));
- },
- onFail(response) {
- pushToStackTrace(response);
- throw deserializeFailure(response, stackTraceWithoutCredentials(stackTrace));
- },
- };
- return transporter.requester.send(payload).then(response => {
- return retryDecision(response, decisions);
- });
- };
- /**
- * Finally, for each retryable host perform request until we got a non
- * retryable response. Some notes here:
- *
- * 1. The reverse here is applied so we can apply a `pop` later on => more performant.
- * 2. We also get from the retryable options a timeout multiplier that is tailored
- * for the current context.
- */
- return createRetryableOptions(transporter.hostsCache, statelessHosts).then(options => {
- return retry([...options.statelessHosts].reverse(), options.getTimeout);
- });
- }
- function createTransporter(options) {
- const { hostsCache, logger, requester, requestsCache, responsesCache, timeouts, userAgent, hosts, queryParameters, headers, } = options;
- const transporter = {
- hostsCache,
- logger,
- requester,
- requestsCache,
- responsesCache,
- timeouts,
- userAgent,
- headers,
- queryParameters,
- hosts: hosts.map(host => createStatelessHost(host)),
- read(request, requestOptions) {
- /**
- * First, we compute the user request options. Now, keep in mind,
- * that using request options the user is able to modified the intire
- * payload of the request. Such as headers, query parameters, and others.
- */
- const mappedRequestOptions = createMappedRequestOptions(requestOptions, transporter.timeouts.read);
- const createRetryableRequest = () => {
- /**
- * Then, we prepare a function factory that contains the construction of
- * the retryable request. At this point, we may *not* perform the actual
- * request. But we want to have the function factory ready.
- */
- return retryableRequest(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Read) !== 0), request, mappedRequestOptions);
- };
- /**
- * Once we have the function factory ready, we need to determine of the
- * request is "cacheable" - should be cached. Note that, once again,
- * the user can force this option.
- */
- const cacheable = mappedRequestOptions.cacheable !== undefined
- ? mappedRequestOptions.cacheable
- : request.cacheable;
- /**
- * If is not "cacheable", we immediatly trigger the retryable request, no
- * need to check cache implementations.
- */
- if (cacheable !== true) {
- return createRetryableRequest();
- }
- /**
- * If the request is "cacheable", we need to first compute the key to ask
- * the cache implementations if this request is on progress or if the
- * response already exists on the cache.
- */
- const key = {
- request,
- mappedRequestOptions,
- transporter: {
- queryParameters: transporter.queryParameters,
- headers: transporter.headers,
- },
- };
- /**
- * With the computed key, we first ask the responses cache
- * implemention if this request was been resolved before.
- */
- return transporter.responsesCache.get(key, () => {
- /**
- * If the request has never resolved before, we actually ask if there
- * is a current request with the same key on progress.
- */
- return transporter.requestsCache.get(key, () => {
- return (transporter.requestsCache
- /**
- * Finally, if there is no request in progress with the same key,
- * this `createRetryableRequest()` will actually trigger the
- * retryable request.
- */
- .set(key, createRetryableRequest())
- .then(response => Promise.all([transporter.requestsCache.delete(key), response]), err => Promise.all([transporter.requestsCache.delete(key), Promise.reject(err)]))
- .then(([_, response]) => response));
- });
- }, {
- /**
- * Of course, once we get this response back from the server, we
- * tell response cache to actually store the received response
- * to be used later.
- */
- miss: response => transporter.responsesCache.set(key, response),
- });
- },
- write(request, requestOptions) {
- /**
- * On write requests, no cache mechanisms are applied, and we
- * proxy the request immediately to the requester.
- */
- return retryableRequest(transporter, transporter.hosts.filter(host => (host.accept & CallEnum.Write) !== 0), request, createMappedRequestOptions(requestOptions, transporter.timeouts.write));
- },
- };
- return transporter;
- }
- function createUserAgent(version) {
- const userAgent = {
- value: `Algolia for JavaScript (${version})`,
- add(options) {
- const addedUserAgent = `; ${options.segment}${options.version !== undefined ? ` (${options.version})` : ''}`;
- if (userAgent.value.indexOf(addedUserAgent) === -1) {
- // eslint-disable-next-line functional/immutable-data
- userAgent.value = `${userAgent.value}${addedUserAgent}`;
- }
- return userAgent;
- },
- };
- return userAgent;
- }
- function deserializeSuccess(response) {
- // eslint-disable-next-line functional/no-try-statement
- try {
- return JSON.parse(response.content);
- }
- catch (e) {
- throw createDeserializationError(e.message, response);
- }
- }
- function deserializeFailure({ content, status }, stackFrame) {
- // eslint-disable-next-line functional/no-let
- let message = content;
- // eslint-disable-next-line functional/no-try-statement
- try {
- message = JSON.parse(content).message;
- }
- catch (e) {
- // ..
- }
- return createApiError(message, status, stackFrame);
- }
- function serializeUrl(host, path, queryParameters) {
- const queryParametersAsString = serializeQueryParameters(queryParameters);
- // eslint-disable-next-line functional/no-let
- let url = `${host.protocol}://${host.url}/${path.charAt(0) === '/' ? path.substr(1) : path}`;
- if (queryParametersAsString.length) {
- url += `?${queryParametersAsString}`;
- }
- return url;
- }
- function serializeQueryParameters(parameters) {
- const isObjectOrArray = (value) => Object.prototype.toString.call(value) === '[object Object]' ||
- Object.prototype.toString.call(value) === '[object Array]';
- return Object.keys(parameters)
- .map(key => encode('%s=%s', key, isObjectOrArray(parameters[key]) ? JSON.stringify(parameters[key]) : parameters[key]))
- .join('&');
- }
- function serializeData(request, requestOptions) {
- if (request.method === MethodEnum.Get ||
- (request.data === undefined && requestOptions.data === undefined)) {
- return undefined;
- }
- const data = Array.isArray(request.data)
- ? request.data
- : { ...request.data, ...requestOptions.data };
- return JSON.stringify(data);
- }
- function serializeHeaders(transporter, requestOptions) {
- const headers = {
- ...transporter.headers,
- ...requestOptions.headers,
- };
- const serializedHeaders = {};
- Object.keys(headers).forEach(header => {
- const value = headers[header];
- // @ts-ignore
- // eslint-disable-next-line functional/immutable-data
- serializedHeaders[header.toLowerCase()] = value;
- });
- return serializedHeaders;
- }
- function stackTraceWithoutCredentials(stackTrace) {
- return stackTrace.map(stackFrame => stackFrameWithoutCredentials(stackFrame));
- }
- function stackFrameWithoutCredentials(stackFrame) {
- const modifiedHeaders = stackFrame.request.headers['x-algolia-api-key']
- ? { 'x-algolia-api-key': '*****' }
- : {};
- return {
- ...stackFrame,
- request: {
- ...stackFrame.request,
- headers: {
- ...stackFrame.request.headers,
- ...modifiedHeaders,
- },
- },
- };
- }
- function createApiError(message, status, transporterStackTrace) {
- return {
- name: 'ApiError',
- message,
- status,
- transporterStackTrace,
- };
- }
- function createDeserializationError(message, response) {
- return {
- name: 'DeserializationError',
- message,
- response,
- };
- }
- function createRetryError(transporterStackTrace) {
- return {
- name: 'RetryError',
- message: 'Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.',
- transporterStackTrace,
- };
- }
- const createSearchClient = options => {
- const appId = options.appId;
- const auth = createAuth(options.authMode !== undefined ? options.authMode : AuthMode.WithinHeaders, appId, options.apiKey);
- const transporter = createTransporter({
- hosts: [
- { url: `${appId}-dsn.algolia.net`, accept: CallEnum.Read },
- { url: `${appId}.algolia.net`, accept: CallEnum.Write },
- ].concat(shuffle([
- { url: `${appId}-1.algolianet.com` },
- { url: `${appId}-2.algolianet.com` },
- { url: `${appId}-3.algolianet.com` },
- ])),
- ...options,
- headers: {
- ...auth.headers(),
- ...{ 'content-type': 'application/x-www-form-urlencoded' },
- ...options.headers,
- },
- queryParameters: {
- ...auth.queryParameters(),
- ...options.queryParameters,
- },
- });
- const base = {
- transporter,
- appId,
- addAlgoliaAgent(segment, version) {
- transporter.userAgent.add({ segment, version });
- },
- clearCache() {
- return Promise.all([
- transporter.requestsCache.clear(),
- transporter.responsesCache.clear(),
- ]).then(() => undefined);
- },
- };
- return addMethods(base, options.methods);
- };
- const customRequest = (base) => {
- return (request, requestOptions) => {
- if (request.method === MethodEnum.Get) {
- return base.transporter.read(request, requestOptions);
- }
- return base.transporter.write(request, requestOptions);
- };
- };
- const initIndex = (base) => {
- return (indexName, options = {}) => {
- const searchIndex = {
- transporter: base.transporter,
- appId: base.appId,
- indexName,
- };
- return addMethods(searchIndex, options.methods);
- };
- };
- const multipleQueries = (base) => {
- return (queries, requestOptions) => {
- const requests = queries.map(query => {
- return {
- ...query,
- params: serializeQueryParameters(query.params || {}),
- };
- });
- return base.transporter.read({
- method: MethodEnum.Post,
- path: '1/indexes/*/queries',
- data: {
- requests,
- },
- cacheable: true,
- }, requestOptions);
- };
- };
- const multipleSearchForFacetValues = (base) => {
- return (queries, requestOptions) => {
- return Promise.all(queries.map(query => {
- const { facetName, facetQuery, ...params } = query.params;
- return initIndex(base)(query.indexName, {
- methods: { searchForFacetValues },
- }).searchForFacetValues(facetName, facetQuery, {
- ...requestOptions,
- ...params,
- });
- }));
- };
- };
- const findAnswers = (base) => {
- return (query, queryLanguages, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Post,
- path: encode('1/answers/%s/prediction', base.indexName),
- data: {
- query,
- queryLanguages,
- },
- cacheable: true,
- }, requestOptions);
- };
- };
- const search = (base) => {
- return (query, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Post,
- path: encode('1/indexes/%s/query', base.indexName),
- data: {
- query,
- },
- cacheable: true,
- }, requestOptions);
- };
- };
- const searchForFacetValues = (base) => {
- return (facetName, facetQuery, requestOptions) => {
- return base.transporter.read({
- method: MethodEnum.Post,
- path: encode('1/indexes/%s/facets/%s/query', base.indexName, facetName),
- data: {
- facetQuery,
- },
- cacheable: true,
- }, requestOptions);
- };
- };
- const LogLevelEnum = {
- Debug: 1,
- Info: 2,
- Error: 3,
- };
- /* eslint no-console: 0 */
- function createConsoleLogger(logLevel) {
- return {
- debug(message, args) {
- if (LogLevelEnum.Debug >= logLevel) {
- console.debug(message, args);
- }
- return Promise.resolve();
- },
- info(message, args) {
- if (LogLevelEnum.Info >= logLevel) {
- console.info(message, args);
- }
- return Promise.resolve();
- },
- error(message, args) {
- console.error(message, args);
- return Promise.resolve();
- },
- };
- }
- function createBrowserXhrRequester() {
- return {
- send(request) {
- return new Promise((resolve) => {
- const baseRequester = new XMLHttpRequest();
- baseRequester.open(request.method, request.url, true);
- Object.keys(request.headers).forEach(key => baseRequester.setRequestHeader(key, request.headers[key]));
- const createTimeout = (timeout, content) => {
- return setTimeout(() => {
- baseRequester.abort();
- resolve({
- status: 0,
- content,
- isTimedOut: true,
- });
- }, timeout * 1000);
- };
- const connectTimeout = createTimeout(request.connectTimeout, 'Connection timeout');
- // eslint-disable-next-line functional/no-let
- let responseTimeout;
- // eslint-disable-next-line functional/immutable-data
- baseRequester.onreadystatechange = () => {
- if (baseRequester.readyState > baseRequester.OPENED && responseTimeout === undefined) {
- clearTimeout(connectTimeout);
- responseTimeout = createTimeout(request.responseTimeout, 'Socket timeout');
- }
- };
- // eslint-disable-next-line functional/immutable-data
- baseRequester.onerror = () => {
- // istanbul ignore next
- if (baseRequester.status === 0) {
- clearTimeout(connectTimeout);
- clearTimeout(responseTimeout);
- resolve({
- content: baseRequester.responseText || 'Network request failed',
- status: baseRequester.status,
- isTimedOut: false,
- });
- }
- };
- // eslint-disable-next-line functional/immutable-data
- baseRequester.onload = () => {
- clearTimeout(connectTimeout);
- clearTimeout(responseTimeout);
- resolve({
- content: baseRequester.responseText,
- status: baseRequester.status,
- isTimedOut: false,
- });
- };
- baseRequester.send(request.data);
- });
- },
- };
- }
- function algoliasearch(appId, apiKey, options) {
- const commonOptions = {
- appId,
- apiKey,
- timeouts: {
- connect: 1,
- read: 2,
- write: 30,
- },
- requester: createBrowserXhrRequester(),
- logger: createConsoleLogger(LogLevelEnum.Error),
- responsesCache: createInMemoryCache(),
- requestsCache: createInMemoryCache({ serializable: false }),
- hostsCache: createFallbackableCache({
- caches: [
- createBrowserLocalStorageCache({ key: `${version}-${appId}` }),
- createInMemoryCache(),
- ],
- }),
- userAgent: createUserAgent(version).add({
- segment: 'Browser',
- version: 'lite',
- }),
- authMode: AuthMode.WithinQueryParameters,
- };
- return createSearchClient({
- ...commonOptions,
- ...options,
- methods: {
- search: multipleQueries,
- searchForFacetValues: multipleSearchForFacetValues,
- multipleQueries,
- multipleSearchForFacetValues,
- customRequest,
- initIndex: base => (indexName) => {
- return initIndex(base)(indexName, {
- methods: { search, searchForFacetValues, findAnswers },
- });
- },
- },
- });
- }
- // eslint-disable-next-line functional/immutable-data
- algoliasearch.version = version;
- export default algoliasearch;
|