UNPKG

feathers-shippo

Version:

A Feathers JS adapter for the Shippo API

245 lines (244 loc) 8.59 kB
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); }); } }