Blog

Use GraphQL to access & monitor remote devices

February 3, 2024

Introduction

If you have already used remote.it, you surely know that remote.it provides a dashboard where you can see your devices, their status, and also make connections. But, what if you have to monitor specific things?

For example, remote.it provides an API you can use with your account key credentials to access data about your devices.

In this article, we’ll learn how to make use of the GraphQL API to build a small React App to monitor your remote devices and events.

remote.it GraphQL API

Actually, remote.it provides a REST API and a GraphQL API as well.

So, before diving into coding, let’s talk about some major differences between REST and GraphQL. This will help us justify our choices.

Organization

REST is compliant with six architectural constraints:

  • Uniform interface.
  • Client-server.
  • Stateless.
  • Cacheable.
  • Layered system.
  • Code on demand (optional)

But, GraphQL is organized into schemas to describe the shape of the data and type system, which help you define various data types that can be used in a GraphQL application.

Format

REST supports a lot of formats like XML, JSON, HTML, or plain text. GraphQL supports only JSON.

Data fetching

To fetch data using a REST API, you can use GET, POST, PUT, PATCH, or DELETE. With GraphQL, you just have to make a POST request with the type of Query (to retrieve data) or mutations (to create, delete or modify an object.)

Those are the major differences between REST and GraphQL.

Let’s dive into coding.

Simple React project

First of all, let’s create a React App project.

yarn create react-app remoteit-react-graphql

Once it’s done and the project is installed, we’ll be adding the axios, boostrap, swr and axios-auth-refresh .

yarn add axios swr axios-auth-refresh react-bootstrap@next bootstrap@5.1.1 react-router-dom

We’ll be using bootstrap to style our web page and axios to make requests to the API.

axios-auth-refresh will be used to retrieve a new token if the current token used for requests has expired.

1 – Authentication using API keys

Before making any requests to the REST and the GraphQL API, we need to use the developer API key.

This API key will be sent on each request with a token to retrieve the data we want.

To access your keys, go to the Account section of the web portal on https://app.remote.it/.

Once it’s done, create a .env file in the directory project. We’ll use this file to store sensitive information such as the developer API KEY and the API URL.

REACT_APP_API_URL=https://api.remote.itREACT_APP_DEV_API_KEY=YOUR DEVELOPER API KEY

Great, now let’s create our own fetcher using axios. Why create now? As we are making requests with a token that will expire, it’ll be useful to claim a new token if this one is expired. We’ll be writing an interceptor.

In the src directory, create a file axios.js.

import axios from “axios”;
import createAuthRefreshInterceptor from “axios-auth-refresh”;
import {useHistory} from “react-router-dom”;

const axiosService = axios.create({
   baseURL: process.env.REACT_APP_API_URL,
   headers: {
       ‘Content-Type’: ‘application/json’,
       ‘apikey’: process.env.REACT_APP_DEV_API_KEY
   }
});

axiosService.interceptors.request.use(async (config) => {
   const token = localStorage.getItem(‘token’);

   if (token){
       config.headers.token = token;
       console.debug(‘[Request]’, config.baseURL + config.url, JSON.stringify(token));
   }

   return config;
})


axiosService.interceptors.response.use(
   (res) => {
       console.debug(‘[Response]’, res.config.baseURL + res.config.url, res.status, res.data);
       return Promise.resolve(res);
   },
   (err) => {
       console.debug(
           ‘[Response]’,
           err.config.baseURL + err.config.url,
           err.response.status,
           err.response.data
       );
       return Promise.reject(err);
   }
);

const refreshAuthLogic = async (failedRequest) => {
   const authHash = localStorage.getItem(‘authHash’)
   const username = localStorage.getItem(‘username’);

   const history = useHistory();
   if (authHash) {
       return axios
           .post(
               ‘/apv/v27/user/login’,
               {
                   username: username,
                   authhash: authHash
               },
               {
                   baseURL: process.env.REACT_APP_API_URL
               }
           )
           .then((resp) => {
               const { token, service_authhash } = resp.data;
               failedRequest.response.config.headers.token = token;
               localStorage.setItem(“authHash”, service_authhash);
               localStorage.setItem(‘token’, token);
           })
           .catch((err) => {
               if (err.response && err.response.status === 401){
                   history.push(‘/login’);
               }
           });
   }
};

createAuthRefreshInterceptor(axiosService, refreshAuthLogic);

export function fetcher(url, data) {
   return axiosService.post(url, data).then((res) => res.data);
}

export default axiosService;

Once it’s done, we can now create the Login page.

Login

The Login page allows entering a username and a password. If the request is successful, then we redirect to the dashboard page.

import React, {useState} from “react”;
import axios from “axios”;
import {useHistory} from “react-router-dom”;

const Login = (key, value) => {

   const history = useHistory();
   const [email, setEmail] = useState(“”);
   const [password, setPassword] = useState(“”);
   const [error, setError] = useState(“”);

   function handleSubmit(event) {
       event.preventDefault();
       axios.post(`${process.env.REACT_APP_API_URL}/apv/v27/user/login`, {
           username: email,
           password: password
       }, {
           headers: {
               “Content-Type”: “application/json”,
               “apikey”: process.env.REACT_APP_DEV_API_KEY
           }
       }).then( r => {
           localStorage.setItem(“username”, email);
           localStorage.setItem(“authHash”, r.data.service_authhash);
           localStorage.setItem(‘token’, r.data.token)
           history.push(‘/home’)
       }).catch(e => {
           setError(e.response.data.reason);
       })

   }

   return (
       <div className=”w-25 mh-100″>
           <form onSubmit={handleSubmit}>
               <div className=”form-group m-2″>
                   <label htmlFor=”email”>Email address</label>
                   <input
                       className=”form-control my-2″
                       required
                       id=”email”
                       type=”email”
                       name=”username”
                       placeholder=”Email address”
                       onChange={(e) => setEmail(e.target.value)}
                   />
               </div>
               <div className=”form-group m-2″>
                   <label htmlFor=”password”>Password</label>
                   <input
                       className=”form-control my-2″
                       id=”password”
                       required
                       type=”password”
                       name=”password”
                       placeholder=”Password”
                       onChange={(e) => setPassword(e.target.value)}
                   />
               </div>
               <button type=”submit” className=”btn btn-primary m-2″>Submit</button>
           </form>
           {error && <div>{error}</div>}
       </div>
   )
}

export default Login;

As you can notice, if the request is successful, we register the token and the service_authhash in the localstorage. The service_authhash will be used to log in again and retrieve a new token.

Usage

The Login page is ready. it’s time to integrate react-router to the project and start defining routes.

import {
 BrowserRouter as Router,
 Switch,
 Route,
} from “react-router-dom”;
import Login from “./Login”;
import Home from “./Home”;
import “bootstrap/dist/css/bootstrap.min.css”;
function App() {
 return (
   <div className=”container-fluid”>
     <Router>
             <Switch>
                 <Route exact path=”/” component={Login} />
                 <Route exact path=”/home” component={Home} />
             </Switch>
     </Router>
   </div>
 );
}

export default App;

Let’s add the Home page now.

Displaying the devices and the most recent events logs

The Home Page will contain two tables: one to display the devices and one for the last events.

We’ll be making queries on the GraphQL API.

So first of all, let’s write the queries we’ll use.

import React from “react”;
import useSWR from ‘swr’
import {fetcher} from “./axios”;


const devicesQuery = {
   query: `{
 login {
   email
   devices(size: 1000, from: 0) {
     total
     hasMore
     items {
       id
       name
       hardwareId
       created
       state        endpoint{geo{latitude longitude}}
     }
   }
 }
}
`
}

const eventsQuery = {
   query: `{
 login {
   events {
     hasMore
     total
     items {
       type
       owner {
         email
       }
       actor {
         email
       }
       target {
         created
         id
         name
       }
       users {
         email
       }
       timestamp
     }
   }
 }
}
`
}

Let’s quickly describe the queries. In general, you can notice that both queries have:

  • login: meaning that the query requires authentication which will return the email of the user
  • the items (devices/events) with parameters such as size. You can also put any filters.
  • total indicates the total number of items and has more remaining numbers that haven’t been fetched.

We can start writing the logic of our component.

But first of all, let’s make requests using SWR and the fetcher we’ve written on the axios.js file.

SWR is React hook for data fetching. It allows data caching and periodic revalidation. Very useful in this case where we want the dashboard to refresh itself periodically.

const Home = () => {

   const dataDevices = useSWR(‘devices’, () => fetcher(‘/graphql/v1’, devicesQuery));

   const dataEvents = useSWR(‘events’, () => fetcher(‘/graphql/v1’, eventsQuery));

 return <div></div>
}

And finally the templating. Let’s create the UI for the dashboard and the tables.

   return (
       <div>
           <div className=”m-5″>
               <h3 className=”h3″>Number of devices: {dataDevices.data?.data?.login?.devices?.total}</h3>
               <table className=”table table-hover”>
                   <thead>
                   <tr>
                       <th scope=”col”>Device id</th>
                       <th scope=”col”>Name</th>
                       <th scope=”col”>hardwareId</th>
                       <th scope=”col”>Created</th>
                       <th scope=”col”>State</th>                        <th scope=”col”>Geo localisation</th>
                   </tr>
                   </thead>
                   <tbody>
                   {
                       dataDevices.data?.data?.login?.devices?.items.map((device, index) =>
                           {
                               console.log(device);
                               return <tr>
                                   <th scope=”row”>{device.id}</th>
                                   <td>{device.name}</td>
                                   <td>{device.hardwareId}</td>
                                   <td>{formatDate(device.created)}</td>
                                   <td className={getStatusColor(device.state)}>{device.state}</td>                                    <td><a href={`https://www.google.com/maps/place/${device.endpoint.geo.latitude},${device.endpoint.geo.longitude}`} target=”_blank”
                                     rel=”noopener noreferrer”>See localisation</a></td>          
                               </tr>
                           }
                       )
                   }
                   </tbody>
               </table>
           </div>

           <hr />

           <div className=”m-5″>
               <h3 className=”h3″>Number of events: {dataEvents.data?.data?.login?.events?.total}</h3>
               <table className=”table table-hover”>
                   <thead>
                   <tr>
                       <th scope=”col”>Type</th>
                       <th scope=”col”>Owner</th>
                       <th scope=”col”>Actor</th>
                       <th scope=”col”>Target Name</th>
                       <th scope=”col”>Time</th>
                   </tr>
                   </thead>
                   <tbody>
                   {
                       dataEvents.data?.data?.login?.events?.items?.map((event, index) => (
                           <tr>
                               <th scope=”row”>{event.type}</th>
                               <td>{event.owner?.email}</td>
                               <td>{event.actor?.email}</td>
                               <td>{event.target?.map((target, index) => (
                                   <p>{target.name} |</p>
                               ))}</td>
                               <td>{event.timestamp}</td>
                           </tr>
                       ))
                   }
                   </tbody>
               </table>
           </div>
       </div>
   )

The dashboard should look like this.

And that’s it. That’s how you can use the remote.it GraphQL API to monitor the events and the status of your devices.

The API provides more features than just retrieving the status of your devices and the events.

You can also :

  • Create a webhook to receive notifications from remote.it
  • Start connections or stop connections to a device
  • Directly manage your access keys.

And also don’t forget that you can make all these requests with REST. Feel free to check the documentation for these here.

Conclusion

To get started using the remote.it API, you can find the documentation here. We’d love to see what fascinating integrations you build with the remote.it API.

And if you want to find the code of this guide, you can check GitHub here.

Related Blogs