networking/index.js

import url from 'url'

/**
 * Helper function to serialize object into urlencoded form data string, properties
 * which value is either `null` or `undefined` will be ignored.
 * @param  {Object} params Object which contains props.
 * @return {string} Result form data string.
 */
function serializeParams(params) {
    let paramStr = ''
    for (let prop in params) {
        if (params[prop] !== null && params[prop] !== void 0)
            paramStr += `&${prop}=${encodeURIComponent(params[prop])}`
    }
    return paramStr
}

/**
 * Helper to perform HTTP requests
 * Blob (binary) content types are not supported
 *
 * Class variables:
 * - baseUrl | authorityUrl: base URL the request path is added to
 * - token: Auth token if the request needs authorization
 *
 * @export
 * @class Client
 */
export default class Client {
    constructor(options = {}) {
        const {authorityUrl, baseUrl, token} = options

        if (!(baseUrl || authorityUrl)) { throw new Error('Missing Azure authority base URL') }
        const parsed = url.parse(baseUrl || authorityUrl)
        this.baseUrl = parsed.protocol === 'https:' || parsed.protocol === 'http:' ? (baseUrl || authorityUrl) : `https://${(baseUrl || authorityUrl)}`
        if (token) {
            this.bearer = `Bearer ${token}`
        }
    }

    post(path, body) {
        let url = this.url(path)
        return this.request('POST', url, body)
    }

    patch(path, body) {
        return this.request('PATCH', this.url(path), body)
    }

    get(path, query) {
        return this.request('GET', this.url(path, query))
    }

    url(path, query) {
        let endpoint = url.resolve(this.baseUrl, path)
        if (query && query.length !== 0) {
            const parsed = url.parse(endpoint)
            parsed.query = query || {}
            endpoint = url.format(parsed)
        }
        return endpoint
    }

    /**
     * Helper function to send HTTP requests
     *
     * @param {String} method
     * @param {String} url
     * @param {Object} [body] - request body
     */
    async request(method, url, body) {
        const options = {
            method: method,
            headers: {
                'Accept': 'application/json, image/*;q=0.1', // image is needed for user phote requests
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }
        if (this.bearer) {
            options.headers['Authorization'] = this.bearer
        }
        if (body) {
            // POST
            options.body = serializeParams(body)
            options.headers['Content-Length'] = options.body.length
        }

        let response = await fetch(url, options)
        const payload = { status: response.status, ok: response.ok, headers: response.headers }

        if (response.headers.get('Content-Type') && response.headers.get('Content-Type').startsWith('image')) {
            try {
                const blob = await response.blob()
                return { ...payload, blob }
            } catch (err) {
                return { ...payload, text: response.statusText }
            }
        } else {
            try {
                const json = await response.json()
                return { ...payload, json }
            } catch (error) {
                try {
                    const text = await response.text()
                    return { ...payload, text }
                } catch (err) {
                    return { ...payload, text: response.statusText }
                }
    
            }
        }            

    }
}