groupby-api
Version:
Client for the GroupBy Searchandiser API.
180 lines • 6.71 kB
JavaScript
import * as clone from 'clone';
import * as fetchPonyfill from 'fetch-ponyfill';
import * as qs from 'qs';
import { Normalizers } from '../utils';
import { Query } from './query';
const SEARCH = '/search';
const REFINEMENTS = '/refinements';
const INVALID_QUERY_ERROR = 'query was not of a recognised type';
const createTimeoutPromise = (timeout) => new Promise((resolve, reject) => {
setTimeout(() => {
reject(new BridgeTimeout(`Timed out in ${timeout} ms`));
}, timeout);
});
export class BridgeTimeout extends Error {
/* istanbul ignore next */
constructor(err) {
super(err);
}
}
export class BridgeError extends Error {
/* istanbul ignore next */
constructor(statusText, status, data) {
super(statusText);
this.status = status;
this.data = data;
Object.setPrototypeOf(this, BridgeError.prototype);
}
get statusText() {
return this.message;
}
}
export const DEFAULT_CONFIG = {
timeout: 1500
};
export class AbstractBridge {
constructor(config) {
this.fetch = fetchPonyfill().fetch;
this.headers = {};
this.config = Object.assign({}, DEFAULT_CONFIG, config);
}
search(query, callback) {
let { request, queryParams } = this.extractRequest(query);
if (request === null)
return this.generateError(INVALID_QUERY_ERROR, callback);
const response = this.fireRequest(this.bridgeUrl, request, queryParams)
.then(AbstractBridge.transformRecords)
.then(AbstractBridge.transformRefinements);
return this.handleResponse(response, callback);
}
refinements(query, navigationName, callback) {
let { request } = this.extractRequest(query);
if (request === null)
return this.generateError(INVALID_QUERY_ERROR, callback);
const refinementsRequest = { originalQuery: request, navigationName };
const response = this.fireRequest(this.refinementsUrl, refinementsRequest);
return this.handleResponse(response, callback);
}
handleResponse(response, callback) {
if (callback) {
response.then((res) => callback(undefined, res), (err) => callback(err));
}
else {
return response;
}
}
extractRequest(query) {
switch (typeof query) {
case 'string': return { request: new Query(query).build(), queryParams: {} };
case 'object': return query instanceof Query
? { request: query.build(), queryParams: query.queryParams }
: { request: clone(query), queryParams: {} };
default: return { request: null, queryParams: null };
}
}
generateError(error, callback) {
const err = new Error(error);
if (callback) {
callback(err);
}
else {
return Promise.reject(err);
}
}
// tslint:disable-next-line max-line-length
fireRequest(url, body, queryParams = {}) {
const options = {
method: 'POST',
headers: Object.assign({ 'Content-Type': 'application/json' }, this.headers),
body: JSON.stringify(AbstractBridge.normalizeRequest(this.augmentRequest(body))),
};
const params = qs.stringify(queryParams);
url = params ? `${url}?${params}` : url;
return Promise.race([this.fetch(url, options), createTimeoutPromise(this.config.timeout)])
.then((res) => {
if (res.ok) {
return res.json();
}
else {
return res.json().then((err) => {
throw new BridgeError(res.statusText, res.status, err);
});
}
})
.catch((err) => {
if (this.errorHandler) {
this.errorHandler(err);
}
throw err;
});
}
static normalizeRequest(request) {
Object.keys(Normalizers).forEach((normalize) => Normalizers[normalize](request));
return request;
}
static transform(response, key, callback) {
if (response[key]) {
return Object.assign(response, { [key]: response[key].map(callback) });
}
else {
return response;
}
}
static transformRecords(response) {
return AbstractBridge.transform(response, 'records', AbstractBridge.convertRecordFields);
}
static transformRefinements(response) {
const transformed = AbstractBridge.transform(response, 'availableNavigation', AbstractBridge.convertRefinement);
return AbstractBridge.transform(transformed, 'selectedNavigation', AbstractBridge.convertRefinement);
}
static convertRecordFields(record) {
const converted = Object.assign(record, { id: record._id, url: record._u, title: record._t });
delete converted._id;
delete converted._u;
delete converted._t;
if (record._snippet) {
converted.snippet = record._snippet;
delete converted._snippet;
}
return converted;
}
static convertRefinement(navigation) {
if (navigation.range) {
navigation.min = Number.MAX_SAFE_INTEGER;
navigation.max = Number.MIN_SAFE_INTEGER;
navigation.refinements = navigation.refinements
.map((ref) => {
navigation.min = Math.min(navigation.min, ref.low);
navigation.max = Math.max(navigation.max, ref.high);
return (Object.assign({}, ref, { high: parseFloat(ref.high), low: parseFloat(ref.low) }));
});
}
return navigation;
}
}
export class CloudBridge extends AbstractBridge {
constructor(clientKey, customerId, config = {}) {
super(config);
this.clientKey = clientKey;
this.baseUrl = `https://${customerId}.groupbycloud.com:443/api/v1`;
this.bridgeUrl = this.baseUrl + SEARCH;
this.refinementsUrl = this.bridgeUrl + REFINEMENTS;
}
augmentRequest(request) {
return Object.assign(request, { clientKey: this.clientKey });
}
}
export class BrowserBridge extends AbstractBridge {
constructor(customerId, https = false, config = {}) {
super(config);
const scheme = https ? 'https' : 'http';
const port = https ? ':443' : '';
this.baseUrl = config.proxyUrl || `${scheme}://${customerId}-cors.groupbycloud.com${port}/api/v1`;
this.bridgeUrl = this.baseUrl + SEARCH;
this.refinementsUrl = this.bridgeUrl + REFINEMENTS;
}
augmentRequest(request) {
return request;
}
}
//# sourceMappingURL=bridge.js.map