import axios, { AxiosRequestConfig, AxiosInstance } from 'axios'; import * as rax from 'retry-axios'; import { AuthenticationClient } from './authentication'; const MaxContentLength = Number.MAX_SAFE_INTEGER; const MaxBodyLength = Number.MAX_SAFE_INTEGER; export function sleep(ms: number) { return new Promise(function(resolve) { setTimeout(resolve, ms); }); } export const DefaultHost = 'https://developer.api.autodesk.com'; export enum Region { US = 'US', EMEA = 'EMEA' } export type IAuthOptions = { client_id: string; client_secret: string; } | { token: string; }; export type IRequestData = { urlencoded: any; } | { json: any; } | { buffer: any; }; export abstract class ForgeClient { protected auth?: AuthenticationClient; protected token?: string; protected root: string; protected host: string; protected region: Region; protected axios: AxiosInstance; /** * Initializes new client with specific authentication method. * @param {string} root Root path for all endpoints (must not contain any slashes at the beginning nor end). * @param {IAuthOptions} auth Authentication object, * containing either `client_id` and `client_secret` properties (for 2-legged authentication), * or a single `token` property (for 2-legged or 3-legged authentication with pre-generated access token). * @param {string} [host="https://developer.api.autodesk.com"] Forge API host (must not contain slash at the end). * @param {Region} [region="US"] Forge availability region ("US" or "EMEA"). */ constructor(root: string, auth: IAuthOptions, host?: string, region?: Region) { if ('client_id' in auth && 'client_secret' in auth) { this.auth = new AuthenticationClient(auth.client_id, auth.client_secret, host); } else if ('token' in auth) { this.token = auth.token; } else { throw new Error('Authentication parameters missing or incorrect.'); } this.root = root; this.host = host || DefaultHost; this.region = region || Region.US; this.axios = axios.create({ baseURL: this.host + '/' + this.root + '/', maxContentLength: MaxContentLength, maxBodyLength: MaxBodyLength }); // Attach an interceptor to the axios instance that will retry response codes 100-199, 429, and 500-599. // For default settings, see https://github.com/JustinBeckwith/retry-axios#usage. this.axios.defaults.raxConfig = { instance: this.axios }; rax.attach(this.axios); } /** * Resets client to specific authentication method, Forge host, and availability region. * @param {IAuthOptions} [auth] Authentication object, * containing either `client_id` and `client_secret` properties (for 2-legged authentication), * or a single `token` property (for 2-legged or 3-legged authentication with pre-generated access token). * @param {string} [host="https://developer.api.autodesk.com"] Forge API host. * @param {Region} [region="US"] Forge availability region ("US" or "EMEA"). */ public reset(auth?: IAuthOptions, host?: string, region?: Region) { if (typeof auth !== 'undefined') { if ('client_id' in auth && 'client_secret' in auth) { this.auth = new AuthenticationClient(auth.client_id, auth.client_secret, host); } else if ('token' in auth) { this.token = auth.token; } else { throw new Error('Authentication parameters missing or incorrect.'); } } if (typeof host !== 'undefined') { this.host = host || DefaultHost; } if (typeof region !== 'undefined') { this.region = region || Region.US; } this.axios = axios.create({ baseURL: this.host + '/' + this.root + '/' }); } protected async setAuthorization(options: any, scopes: string[]) { options.headers = options.headers || {}; if (this.auth) { const authentication = await this.auth.authenticate(scopes); options.headers['Authorization'] = 'Bearer ' + authentication.access_token; } else { options.headers['Authorization'] = 'Bearer ' + this.token; } } // Makes a general request and returns the entire response (not just its parsed body) protected async fetch(config: AxiosRequestConfig) { return this.axios.request(config); } // Helper method for GET requests, // returning parsed response body or throwing an excetion in case of an issue protected async get(endpoint: string, headers: { [name: string]: string } = {}, scopes: string[]): Promise { const config: AxiosRequestConfig = { headers }; await this.setAuthorization(config, scopes); const resp = await this.axios.get(endpoint, config); return resp.data; } // Helper method for GET requests returning binary data, // throwing an excetion in case of an issue protected async getBuffer(endpoint: string, headers: { [name: string]: string } = {}, scopes: string[]): Promise { const config: AxiosRequestConfig = { headers, responseType: 'arraybuffer' }; await this.setAuthorization(config, scopes); const resp = await this.axios.get(endpoint, config); return resp.data; } // Helper method for GET requests returning stream data, // throwing an excetion in case of an issue protected async getStream(endpoint: string, headers: { [name: string]: string } = {}, scopes: string[]): Promise { const config: AxiosRequestConfig = { headers, responseType: 'stream' }; await this.setAuthorization(config, scopes); const resp = await this.axios.get(endpoint, config); return resp.data; } // Helper method for POST requests, // returning parsed response body of throwing an excetion in case of an issue protected async post(endpoint: string, data: any, headers: { [name: string]: string } = {}, scopes: string[]): Promise { const config: AxiosRequestConfig = { headers }; await this.setAuthorization(config, scopes); const resp = await this.axios.post(endpoint, data, config); return resp.data; } // Helper method for PUT requests, // returning parsed response body of throwing an excetion in case of an issue protected async put(endpoint: string, data: any, headers: { [name: string]: string } = {}, scopes: string[]): Promise { const config: AxiosRequestConfig = { headers }; await this.setAuthorization(config, scopes); const resp = await this.axios.put(endpoint, data, config); return resp.data; } // Helper method for PATCH requests, // returning parsed response body of throwing an excetion in case of an issue protected async patch(endpoint: string, data: any, headers: { [name: string]: string } = {}, scopes: string[]): Promise { const config: AxiosRequestConfig = { headers }; await this.setAuthorization(config, scopes); const resp = await this.axios.patch(endpoint, data, config); return resp.data; } // Helper method for DELETE requests, // returning parsed response body of throwing an excetion in case of an issue protected async delete(endpoint: string, headers: { [name: string]: string } = {}, scopes: string[]): Promise { const config: AxiosRequestConfig = { headers }; await this.setAuthorization(config, scopes); const resp = await this.axios.delete(endpoint, config); return resp.data; } }