npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cisco-meraki/dashboard-api-tools

v1.0.7

Published

Typescript library for interacting with Meraki's public API

Downloads

30

Readme

@cisco-meraki/dashboard-api-tools

Typescript SDK for interacting with Meraki's API. This library provides an interface for Javascript and Typescript applications to interact with Cisco Meraki's cloud-managed platform.

Features:

  • Support for interacting with Meraki's Dashboard endpoints via apiRequest. This provides a wrapper around JavaScript's native fetch function with features specific to handling Meraki's API responses.
  • Support for pagination via makePaginatedRequest
  • Supports error handling for Meraki's standard API error responses
  • Supports Action Batches via batchedApiRequest
  • Provides a React hook to make API requests directly from React components via useApiRequest
  • Provides a React Toolkit Query base query function via fetchBaseQuery
  • Automatic retries on API requests that fail due to rate limiting errors

Install

yarn add @cisco-meraki/dashboard-api-tools
npm install @cisco-meraki/dashboard-api-tools

How to Use

Making API Requests

apiRequest()

apiRequest acts as a wrapper around Javascript's fetch that is strongly-typed and supports Meraki's API-specific features. Note that requests to Meraki's API require an API Key. See the section on Providing API Key below.

Accepted Parameters:

  • method: HTTP method of API request. Must be one of "get", "post", "put", "delete", "options", "GET", "POST", "PUT", "DELETE", "OPTIONS"
  • url: URL of API request,
  • data?: Optional. Payload body for API request. Do not use this for GET requests. Instead, any request parameters for GET requests should be supplied in the URL as query parameters.
  • options?: Optional
    • fetchOptions: Object that contains fields to override default parameters passed to fetch. This is where you would add any headers that you'd like to send to your API request, which may be useful when trying to troubleshoot CORS issues (see Troubleshooting section below)
    • auth: Object that contains fields required for authenticating the API requests
      • apiKey: User's API key

Response

It will return objects in two possible shapes, wrapped in a promise.

For successful API requests:

Promise<{
  firstPageUrl: string | null;
  lastPageUrl: string | null;
  nextPageUrl: string | null;
  prevPageUrl: string | null;
  linkHeader: string | null;
  retryAfter: number | null;
  errors: null;
  ok: true;
  statusCode: number;
  statusText: string;
  data: ResponseData
}>

Note that data is typed as a generic type ResponseData. This generic type will be passed when calling apiRequest<ResponseData>() and allows the consumer to set the expected data structure of the API response.

For unsuccessful API requests:

Promise<{
  errors: string[];
  ok: false;
  statusCode: number;
  statusText: string;
}>

Providing API Key

In order to interact with the Meraki Dashboard API, you'll need to provide your API key. If you need help obtaining this key, follow the steps in our developer documentation.

await apiRequest(
  "POST",
  "www.some-url.com/api/v1/endpoint",
  { ... },
  {
    auth: {
      apiKey: <your API key>
    }
  }
);

Usage

import { apiRequest } from "@cisco-meraki/dashboard-api-tools";

...

const url = `/api/v1/networks/${networkId}/webhooks/webhookTests`
const data = {
  url: "https://webhook.site/#!/61296b81-3980-4473-89e9-4b3c8ef0a70e",
  payloadTemplateId: "wpt_00002",
}

await apiRequest<ResponseObjectType>("POST", url, data)

Note that ResponseObjectType is whatever type you expect the response object from the API request to be in.

This function is compatible with either async/await or promise chaining

async/await example
try {
  await apiRequest("GET", "www.some-url.com/api/v1/endpoint");
  /* carry on */
} catch (badResponse) {
  /* do something with badResponse.errors */
}
promise chaining example
apiRequest("GET", "www.some-url.com/api/v1/endpoint")
  .then(() => /* carry on */)
  .catch((badResponse) => /* do something with badResponse.errors */);

isApiError()

For checking errors, the library also provides a type guard helper function to ensure that the errors are in the format we expect before using them. It will verify that the failed response object contains an error field that is an array of strings.

Accepted Parameters:

  • response: The return object received from the call to apiRequest()

Usage

import { apiRequest, isApiError } from "@cisco-meraki/dashboard-api-tools";

...

try {
  await apiRequest("GET", "www.some-url.com/api/v1/endpoint");
  /* carry on */
} catch (badResponse) {
  if(isApiError(badResponse)) {
    /* do something with badResponse.errors */
  } else {
    /* we don't know what format errors are in, so just log them */
  }
}

Pagination

The return object of apiRequest() includes firstPageUrl, lastPageUrl, prevPageUrl and nextPageUrl fields that can be used by subsequent requests to get paginated data. If you want to automatically make requests to nextPageUrl across multiple requests, this package provides a helper function for that functionality.

paginatedApiRequest()

This function will make paginated requests to the provided URL based on the perPage query parameter. It makes a request to the given URL, then makes successive requests to URLs provided in the Link header from the response. See docs on pagination for more details on the Link header.

It accepts functions that will be called for each successful response as well as each unsuccessful response.

It also accepts a parameter to set the maximum number of requests allowed for this endpoint to protect against abnormally large or infinite number of successive requests.

The method signature accepts the above 3 values as well as an object with a shape identical to the arguments provided for apiRequest()

Accepted Parameters:

  • dataHandler: Callback that is invoked every time a successful response is received. For example, this can be a handler that dispatches a Redux action every time data is received from an endpoint.
  • errorHandler: Callback that is invoked each time a response returns errors
  • apiRequestParams: Object that contains fields for each parameter used for apiRequest()
  • maxRequests: Maximum number of paginated requests that will be made before halting all requests. The default value is 9,999.

Usage

import { paginatedApiRequest } from "@cisco-meraki/dashboard-api-tools";

...

const storeClientsInRedux = (clients) => (dispatch) => {
  dispatch(actions.updateClients(clients));
};

const storeErrorsInRedux = (errors) => (dispatch) => {
  dispatch(actions.clientFetchFailed(errors));
};

await paginatedApiRequest(storeClientsInRedux, storeErrorsInRedux, {method: "GET", url: "www.some-url.com/api/v1/endpoint}, 100);

Action Batches

Action Batches are a special type of Dashboard API mechanism for submitting batched configuration requests in a single synchronous or asynchronous transaction. Action Batches are ideal for bulk configuration, either in the initial provisioning process, or for rolling out wide-scale configuration changes. For example, you could add a switch to a network, configure all 48 ports, and set the switch’s management interface in a single POST request.

batchedApiRequest()

batchedApiRequest acts as a wrapper for Meraki's Action Batches. When the request is first made and the Action Batch is initially created, the status may be "pending" while waiting for the batches to complete. If so, it will poll the status of the action batch and return either an error or successful response depending on the Action Batch's status.

Accepted Parameters:

  • organizationId: Id of the organization to run the series of API requests on
  • actions: Object that contains information around which API requests to include in the Action Batch. Contains these fields:
    • resource: URL fragment of API request
    • operation: Must be "create", "update" or "destroy"
    • body: Payload body for API request
  • authOptions: Object that contains fields required for authenticating the API requests
    • fetchOptions: Optional options to override parameters passed to fetch call
    • auth: Object used to store authentication options
      • apiKey: User's API key
  • opts?: Optional. Object that contains extra metadata for how you want the Action Batch to perform. Contains these fields:
    • maxPollingTime: Maximum time (in ms) before it halts requests that check on Action Batch status. Defaults to 12,000ms
    • auth: Time (in ms) between each request to check if Action Batch is complete. Defaults to 500ms
    • synchronous: Flag that tells the Action Batch to run synchronously or asynchronously

Response

For successful requests:

Promise<{
  id: string;
  organizationId: string;
  confirmed: boolean;
  synchronous: boolean;
  status: ActionBatchStatus;
  actions: Action[];
}>

For unsuccessful requests:

Promise<{
  errors: string[];
  ok: false;
  statusCode: number;
  statusText: string;
}>

Usage

import { batchedApiRequest, isApiError } from "@dashboard-api/api-utils";

...

const actions = [
  {
    "resource": "/devices/QXXX-XXXX-XXXX/switchPorts/3",
    "operation": "update",
    "body": { "enabled": true }
  };
  ...
]

const authOptions = {
  auth: {
    apiKey: "your apiKey
  }
}
try {
  await batchedApiRequest(organizationId, actions, authOptions)
  /* carry on */
} catch (badResponse) {
  if (isApiError(badResponse)) {
    /* do something with badResponse.errors */
  } else {
    /* handle the errors in any way of your choice */
  }
}

React Hook

If using React and not using a library such as Redux Toolkit Query that provide hooks for you, you may find some use in a custom React hook that provides consistent data fetching across components.

useApiRequest()

You can use this function as a React hook for interacting with Meraki's public API. It is a wrapper around React's useState and useEffect hooks and uses isApiError() to make API requests and format the responses.

It has a method signature that accepts a generic type that represents the expected response object type, as well as two arguments:

  • apiRequestParams: An object with a the shape identical to the arguments provided for apiRequest()
  • dependencies: A list of dependencies that, when changed, will trigger this hook to run. This is similar to how React's useEffect hook works

It returns 3 values:

  • response: The formatted response from the API. It will be undefined if request was not successful.
  • errors: Any errors returned from API response. It will be undefined if request was successful. The hook uses isApiError() to ensure that the errors returned are wrapped in an array of strings.
  • isFetching: Status indicating whether the API request completed or not. This is useful for dynamically rendering a loading state in the UI.

Usage

import { useApiRequest } from "@cisco-meraki/dashboard-api-tools";

...

const ComponentUsingHook = () => {
  const [response, errors, isFetching] = useApiRequest<SuccessfulResponse>({method: "GET", url: "www.some-url.com/api/v1/endpoint" }, []);

  return (
    <>
      {
        isFetching && <div>Loading</div>
      }
      {
        !isFetching && response && <div>{response.data.someValueFromResponse}</div>
      }
      {
        !isFetching && errors && <div>
          {
            errors.map((error, index) => <div key={index}>{error}</div>)
          }
        </div>
      }
    </>
  );
};

Redux Toolkit Query Integration

Redux Toolkit Query provides an opinionated pattern for Redux logic in your React applications intended to simplify things for the developer. If your application is using RTK Query, you can use the custom base query function provided.

fetchBaseQuery

RTK Query allows for a custom base query, which is usually just a wrapper around Javascript's native fetch, to be provided to customize data handling and response objects across endpoints. For this case, we have created the fetchBaseQuery function that integrates with RTK Query and provides a consistent way of making API requests while still allowing individual endpoints to customize requests and responses as needed.

Because fetchBaseQuery is using apiRequest(), we can access all of the response data that we expect from our API responses.

The return object of this function includes these fields:

  • data - This is the data returned in the response object from the API request. This comes from the data field from apiRequest()
  • meta - This includes all other data from apiRequest() that is not part of the response object (i.e. statusCode, pagination fields, etc.). See documentation above for all fields in the return object form apiRequest().

Note that responseHandler and validateStatus, which are expected to be part of RTK Query responses, are not yet available when using this custom base query.

Usage

import { fetchBaseQuery } from "@cisco-meraki/dashboard-api-tools";

...

export const merakiApi = createApi({
  reducerPath: "merakiApi",
  baseQuery: fetchBaseQuery({
    baseUrl: "api.meraki.com/api/v1/",
    transformHeaders: (headers: Headers) =>{
      const preparedHeaders = new Headers(headers);

      preparedHeaders.set("Accept", "application/json");
      /* add other headers here */

      return Promise.resolve(preparedHeaders);
    },
  }),
  endpoints: () => ({}),
});

Troubleshooting

CORS issues

The same-origin policy limits the ability of a browser to use resources from a server outside of its domain to help prevent forging and stealing private data. Because of this, websites can generally only fetch data from their own servers. CORS is a mechanism implemented by browsers that uses HTTP headers that let the user request data across domains.

Any application that you build that wants to fetch Meraki Dashboard data will be on a different domain and will therefore need to account for CORS. Our recommendation is to add a simple CORS proxy to your application.

CORS proxies will allow you to fetch the intended data from Meraki's API. Instead of requesting data directly from a Meraki endpoint, you'll send requests to the CORS proxy server instead. The proxy server will then forward the request to Meraki's API as normal and forward the response back to the client. This server is also a great place to add you API key to Meraki API requests.

Example

import { apiRequest } from "@cisco-meraki/dashboard-api-tools";
...
const fetchOptions = {
  headers: {
    # This header will need to be supported by your CORS proxy
    "Custom-Header-Used-By-Proxy": "https://api.meraki.com/api/v1/organizations",
  }
}

# Use the URL of the CORS proxy instead of api.meraki.com
# The CORS proxy will then forward requests to the desired endpoint using "Custom-Header-Used-By-Proxy"
apiRequest("GET", "http://<PROXY-URL>/", undefined, { fetchOptions: fetchOptions })