dappbot-api-client
A JS package for client-side interaction with the DappBot API. Provides interfaces roughly like:
import API from '@eximchain/dappbot-api-client';
import request from 'request-promise-native';
const creds = {
username : 'testing@example.com',
password : 'secret-from-user'
}
const loginResponse = await request(API.auth.login.request(creds))
Highlights:
- Designed to work seamlessly with
react-request-hook.
- Compatible with
axios, request, and fetch.
- Written in Typescript & depends on our shared
@eximchain/dappbot-types package, so if you are a Typescript user, most of the API will autocomplete for you.
- Works equally well in browsers and node.
Usage
Installation
npm install @eximchain/dappbot-api-client @eximchain/dappbot-types
Configuration
The Dappbot client needs to be configured with:
- The API's URL
- The current authentication data
- A setter to update that authentication data
We let the consumer handle storing that data in order to be compatible with more environments. Here is an example of how a React app would configure that at the root component:
import React, { FunctionComponent, useState } from 'react';
import DappbotAPI from '@eximchain/dappbot-api-client';
import DappbotUser from '@eximchain/dappbot-types/spec/user';
import DappBody from './components/DappBody';
export function APIProvider(props){
const [dappbotAuth, setDappbotAuth] = useState(DappbotUser.newAuthData());
const API = new DappbotAPI({
dappbotUrl : 'https://api.dapp.bot',
authData : dappbotAuth,
setAuthData : setDappbotAuth
})
return <DappBody API={API} />
}
export default APIProvider;
Interacting with the API
Accessing the Methods
Each method is mounted within its root resource, for instance:
- Login request:
API.auth.login
- Create dapp request:
API.private.create
The methods then all provide different factory functions depending on which request solution you're using. These methods synchronously return fully configured request objects, including a fully scoped path, headers with Authorization if necessary, and a properly formatted body. Note that the methods do not actually perform the request, they just produce the appropriate object for the request solution you're using:
import { useResource } from 'react-request-hook';
import request from 'request-promise-native';
import fetch from 'node-fetch';
import axios from 'axios';
const creds = {
username : 'testing@example.com',
password : 'secret-from-user'
}
const [resourceResponse, requestLogin] = useResource(API.auth.login.resource);
const requestResponse = await request(API.auth.login.request(creds));
const fetchResponse = await fetch(API.auth.login.fetch(creds));
const axiosResponse = await axios(API.auth.login.axios(creds));
Method Arguments
All of the factory methods have their arguments appropriately typed. If a call has no body, then no arg object needs to be provided. If the call's path includes a variable (e.g. dapp management calls), then the function will instead take two arguments: one for the name, one for the body:
const ReadPaymentReq = API.payment.read.request()
const ReadPaymentRes = await request(ReadPaymentReq);
const ReadDappReq = API.private.read.request('DappName')
const ReadDappRes = await request(ReadReq)
const NewDappConfig = { ... };
const CreateReq = API.private.create.request('NewDappName', NewDappConfig)
const CreateRes = await request(CreateReq)
Validation
You can also use the underlying type system to validate & interpret the decoded API responses:
import Types from '@eximchain/dappbot-types';
const CreateName = '...';
const CreateArgs = { ... }
if (Types.Methods.Private.CreateDapp.isArgs(CreateArgs)) {
const CreateRes = await request(CreateReq) as Types.Methods.Private.CreateDapp.Response;
if (Types.Response.isSuccessResponse(CreateRes)) {
} else {
}
}
Maintaining Auth in Long Sessions
If you are using this library in a web application, it's important to note that the Authorization values returns from the login method expires after 1 hour. If the user sits on the page for a Long Time and then tries to make a request, you need to refresh their authorization. You could do this manually with the provided method, but the API provides a helper function which handles it for you:
const RefreshedAPI = await API.refreshAuth();
const DappList = await request(RefreshedAPI.private.list.request())
refreshAuth() inspects the authData config parameter and follows these steps:
- If the
RefreshToken is empty, then it throws an error asking you to log in.
- If there is a
RefreshToken, check the ExpiresAt field to see if it's actually expired.
- If the token is still valid, the method returns the same
API instance.
- If the token is expired, it calls
API.auth.refresh and then uses the setAuthData config to update the stored authData. It then returns a new API instance.
Declarative Caveat
The 4th step above is essential when using this library in a declarative environment like React. If you call refreshAuthorization() in an async function and it updates, the original API object will now be out of date in the current execution. By returning a new API object, shallow object comparisons (e.g. prop comparisons) will detect when a stale instance has been replaced by a fresh one.
Here is a heavily commented example of applying in a React component which refreshes a user's stale auth data before requesting their dapp list. Note the use of helper methods to detect auth status:
import React, { useEffect } from 'react';
import { useResource } from 'react-request-hook'
import APIType from '@eximchain/dappbot-api-client'
interface FetcherProps {
API : APIType
}
export function DappListFetcher({ API }:FetcherProps) {
const [dappList, requestDappList] = useResource(API.private.list.resource);
async function getList() {
if (API.hasActiveAuth()) {
requestDappList();
}
if (API.hasNoAuth()) {
goToLoginPage();
}
if (API.hasStaleAuth()) {
try {
await API.refreshAuth();
} catch (err) {
console.log('Error refreshing auth: ',err);
}
}
}
useEffect(function fetchListOnStartAndAPI() {
getList();
}, [API])
return (
<DappListPresentation dapps={dappList.data} />
)
}
export default DappListFetcher;
Development
This library essentially just uses wrapper functions to instrument Dappbot's underlying types in @eximchain/dappbot-types, so depending on how you are modifying this client, you may need to update those types. They are installed via npm, but while you are developing, it's easier to install them from the file system. Here is how you do that with npm:
~ git clone https://github.com/Eximchain/dappbot-types.git
~ git clone https://github.com/Eximchain/dappbot-api-client.git
~ cd dappbot-api-client
~ npm uninstall @eximchain/dappbot-types
~ npm install ../dappbot-types
Once you've done that, you'll be able to update the type source, save the file, and have those changes immediately reflected here. If you make a PR here which depends on type changes, make sure to include a link to your PR on @eximchain/dappbot-types as well.