@cisco-meraki/dashboard-api-tools
Version:
Typescript library for interacting with Meraki's public API
131 lines (130 loc) • 6.63 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.paginatedApiRequest = exports.isApiError = exports.apiRequest = void 0;
const httpMethods = ["get", "post", "put", "delete", "options", "GET", "POST", "PUT", "DELETE", "OPTIONS"];
const extractUrlFromLinkHeader = (linkHeader, targetRel) => {
if (!linkHeader)
return null;
const links = linkHeader.split(", ").map((link) => {
const [url, r] = (link.match(/^<([^>]+)>; rel=(.+)$/) || []).slice(1);
if (url && r)
return { url, rel: r };
});
const link = links.find((link) => (link === null || link === void 0 ? void 0 : link.rel) === targetRel);
return link ? link.url : null;
};
const extractCustomHeaders = (response) => {
var _a, _b;
const linkHeader = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get("Link");
const retryAfterHeader = (_b = response.headers) === null || _b === void 0 ? void 0 : _b.get("Retry-After");
const parsedRetryHeader = retryAfterHeader ? parseInt(retryAfterHeader, 10) : null;
return {
statusCode: response.status,
statusText: response.statusText,
errors: null,
ok: true,
retryAfter: parsedRetryHeader || null,
firstPageUrl: linkHeader ? extractUrlFromLinkHeader(linkHeader, "first") : null,
prevPageUrl: linkHeader ? extractUrlFromLinkHeader(linkHeader, "prev") : null,
nextPageUrl: linkHeader ? extractUrlFromLinkHeader(linkHeader, "next") : null,
lastPageUrl: linkHeader ? extractUrlFromLinkHeader(linkHeader, "last") : null,
linkHeader,
};
};
const successResponse = (response) => __awaiter(void 0, void 0, void 0, function* () {
let responseData;
try {
responseData = yield response.json();
}
catch (_a) {
responseData = {};
}
const responseMetadata = extractCustomHeaders(response);
return Promise.resolve(Object.assign({ data: responseData }, responseMetadata));
});
const failureResponse = (response) => __awaiter(void 0, void 0, void 0, function* () {
let errors;
try {
const errorData = yield response.json();
errors = (errorData === null || errorData === void 0 ? void 0 : errorData.errors) || [];
}
catch (_b) {
errors = ["Could not parse errors from response"];
}
return Promise.reject({
errors,
statusCode: response.status,
statusText: response.statusText,
ok: response.ok,
});
});
const apiRequest = (method, url, data, options) => __awaiter(void 0, void 0, void 0, function* () {
var _c, _d;
const authHeaders = {};
if ((_c = options === null || options === void 0 ? void 0 : options.auth) === null || _c === void 0 ? void 0 : _c.csrfToken) {
authHeaders["X-CSRF-TOKEN"] = options.auth.csrfToken;
}
if ((_d = options === null || options === void 0 ? void 0 : options.auth) === null || _d === void 0 ? void 0 : _d.apiKey) {
authHeaders["X-Cisco-Meraki-API-Key"] = options.auth.apiKey;
}
const fetchOptions = Object.assign({ method: method, headers: Object.assign({ "Content-Type": "application/json", Accept: "application/json" }, authHeaders), redirect: "follow", referrerPolicy: "strict-origin-when-cross-origin" }, options === null || options === void 0 ? void 0 : options.fetchOptions);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const totalRetries = 5;
let attempt = 0;
if (typeof data !== "undefined") {
fetchOptions.body = JSON.stringify(data);
}
let response = yield fetch(url, fetchOptions);
while (response.status === 429 && attempt < totalRetries) {
const retrySec = extractCustomHeaders(response).retryAfter || 1.5 ** (attempt + 1);
yield sleep((retrySec + Math.random()) * 1000);
response = yield fetch(url, fetchOptions);
attempt += 1;
}
return response.ok ? successResponse(response) : failureResponse(response);
});
exports.apiRequest = apiRequest;
const isApiError = (response) => {
const errorResponse = response;
const hasApiErrors = Array.isArray(errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.errors) && (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.errors.every((error) => typeof error === "string"));
return (hasApiErrors &&
(errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.ok) === false &&
typeof (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.statusCode) === "number" &&
typeof (errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.statusText) === "string");
};
exports.isApiError = isApiError;
const makePaginatedRequest = (dataHandler, errorHandler, apiRequestParams, maxRequests, requestCount) => __awaiter(void 0, void 0, void 0, function* () {
if (requestCount >= maxRequests) {
return;
}
try {
const { method, url } = apiRequestParams;
const apiResp = yield apiRequest(method, url);
const { data: responseData, nextPageUrl } = apiResp;
dataHandler(responseData);
if (nextPageUrl) {
yield makePaginatedRequest(dataHandler, errorHandler, Object.assign(Object.assign({}, apiRequestParams), { url: nextPageUrl }), maxRequests, ++requestCount);
}
}
catch (badResponse) {
if (isApiError(badResponse)) {
errorHandler(badResponse.errors);
}
else {
throw new Error("Paginated API request failed with unknown error.");
}
}
});
const paginatedApiRequest = (dataHandler, errorHandler, apiRequestParams, maxRequests = 9999) => __awaiter(void 0, void 0, void 0, function* () {
yield makePaginatedRequest(dataHandler, errorHandler, apiRequestParams, maxRequests, 0);
});
exports.paginatedApiRequest = paginatedApiRequest;