feathers-shippo
Version:
A Feathers JS adapter for the Shippo API
245 lines (244 loc) • 8.59 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import * as errors from '@feathersjs/errors';
import Bottleneck from 'bottleneck';
import axios from 'axios';
const SHIPPO_API_URL = 'https://api.goshippo.com';
[];
export const calcMinTime = (perMinute) => 60000 / perMinute;
// https://goshippo.com/docs/rate-limits/
const liveMinTimes = {
create: calcMinTime(500),
update: calcMinTime(500),
get: calcMinTime(4000),
find: calcMinTime(50),
remove: calcMinTime(500) // Note most services don't actually have remove
};
const testMinTimes = {
create: calcMinTime(50),
update: calcMinTime(50),
get: calcMinTime(400),
find: calcMinTime(5),
remove: calcMinTime(50) // Note most services don't actually have remove
};
export const shippoLimiter = (method, minTimes) => {
const limiter = new Bottleneck({ minTime: minTimes[method] });
// TODO: Inspect the error for a timeoute/expiry to retry after
limiter.on('failed', (error, jobInfo) => __awaiter(void 0, void 0, void 0, function* () {
if (error.code === 429 && jobInfo.retryCount === 0) {
// Retry once after 200ms
return 200;
}
}));
return limiter;
};
export const shippoLimiters = (token) => {
if (!token) {
throw new errors.GeneralError('options.token is required a Shippo API Token for new ShippoService()');
}
const minTimes = token.startsWith('shippo_live')
? liveMinTimes
: testMinTimes;
return {
create: shippoLimiter('create', minTimes),
update: shippoLimiter('update', minTimes),
get: shippoLimiter('get', minTimes),
find: shippoLimiter('find', minTimes),
remove: shippoLimiter('remove', minTimes)
};
};
export const axiosOpts = (params) => {
return {
params: params === null || params === void 0 ? void 0 : params.query
};
};
const shippoResource = (service) => {
const { path } = service.options;
return {
create: (data, params) => {
return service.schedule('create', () => __awaiter(void 0, void 0, void 0, function* () {
return service.shippo.post(path, data, axiosOpts(params));
}));
},
update: (id, data, params) => {
return service.schedule('update', () => {
if (!id) {
throw new errors.BadRequest('ID is required');
}
return service.shippo.put(`${path}/${id}`, data, axiosOpts(params));
});
},
get: (id, params) => {
return service.schedule('get', () => {
if (!id) {
throw new errors.BadRequest('ID is required');
}
return service.shippo.get(`${path}/${id}`, axiosOpts(params));
});
},
find: (params) => {
return service.schedule('find', () => __awaiter(void 0, void 0, void 0, function* () {
return service.shippo.get(path, axiosOpts(params));
}));
},
remove: (id, params) => {
return service.schedule('remove', () => {
return service.shippo.delete(`${path}/${id}`, axiosOpts(params));
});
}
};
};
export const shippo = (token) => {
return axios.create({
baseURL: SHIPPO_API_URL,
headers: {
Authorization: `ShippoToken ${token}`,
'Accept-Encoding': null
}
});
};
export class ShippoService {
constructor(options, app) {
this.options = options;
this.app = app;
const { token, path } = options;
if (!token) {
throw new errors.GeneralError('options.token is required a Shippo API Token for new ShippoService()');
}
if (!path) {
throw new errors.GeneralError(`${path} is invalid options.path for new ShippoService()`);
}
this.shippo = shippo(token);
this.resource = shippoResource(this);
}
handleError(error, params) {
if (params) {
params.shippoError = error;
}
if (!error.response) {
throw error;
}
const FeathersError = errors[error.response.status];
if (FeathersError) {
throw new FeathersError(error.message, error.response.data);
}
throw new errors.BadRequest(error.message || error, error);
}
handleMethod(method) {
const { methods, path } = this.options;
if (!methods.includes(method)) {
throw new errors.NotImplemented(`"${method}" not implemented on "${path}" ShippoService`);
}
}
handleResult(result, params) {
if (params) {
params.shippoResult = result;
}
if (result.data.results) {
result.data.data = result.data.results;
delete result.data.results;
}
if (result.data.count) {
result.data.total = result.data.count;
delete result.data.count;
}
return result.data;
}
schedule(method, fn) {
const { limiters } = this.options;
if (limiters && limiters[method]) {
return limiters[method].schedule(fn);
}
return fn();
}
_create(data, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('create');
return this.resource
.create(data, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
create(data, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._create(data, params);
});
}
_get(id, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('get');
return this.resource
.get(id, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
get(id, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._get(id, params);
});
}
_find(params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('find');
return this.resource
.find(params)
.then(this.handleResult)
.catch(this.handleError);
});
}
find(params) {
return __awaiter(this, void 0, void 0, function* () {
return this._find(params);
});
}
_update(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('update');
return this.resource
.update(id, data, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
update(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._update(id, data, params);
});
}
_patch(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('patch');
return this._update(id, data, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
patch(id, data, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._patch(id, data, params);
});
}
_remove(id, params) {
return __awaiter(this, void 0, void 0, function* () {
this.handleMethod('remove');
return this.resource
.remove(id, params)
.then(this.handleResult)
.catch(this.handleError);
});
}
remove(id, params) {
return __awaiter(this, void 0, void 0, function* () {
return this._remove(id, params);
});
}
}