UNPKG

@cisco-meraki/dashboard-api-tools

Version:

Typescript library for interacting with Meraki's public API

131 lines (130 loc) 6.63 kB
"use strict"; 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;