Source: nodeClient.js

/**
 * Module léger pour node ne contenant les méthodes utiles pour récupérer des données sur une sésathèque
 * @module nodeClient
 */
/* eslint-env node */
'use strict'
// les modules node, on aura sûrement besoin que de l'un des deux, on ne les initialisera qu'au premier appel
// const http = require('http')
// const https = require('https')

const { URL } = require('url')

const { getBaseUrl } = require('./sesatheques')
// on se passe de lui, car y'a aussi des imports là-bas
// import {getDataUrl} from './internals'
// pas terrible mais en attendant la gestion des import es6 par nodejs on duplique cette fct

/**
 * Retourne l'url absolue à passer à callApiUrl pour récupérer les datas
 * @param {string} rid
 * @param {{isPublic: boolean, isRef: boolean}} options (props à false par défaut), si isPublic est absent on le déduit de shouldForcePublic(baseId)
 * @return {string}
 */
function getDataUrl (rid, options) {
  if (!options) options = {}
  // args ok, id peut être un rid avec baseId null
  const isPublic = Boolean(options.isPublic)
  const isRef = Boolean(options.isRef)
  const [baseId, id] = rid.split('/')
  let url = getBaseUrl(baseId) + 'api/' + (isPublic ? 'public/' : 'ressource/') + id
  if (isRef) url += '?format=ref'
  return url
}

/**
 * Appelle une url de la sesathèque et retourne le résultat
 * @param url
 * @param {Object} options
 * @param {string} [options.apiToken] token brut (on le passera à encodeURIComponent)
 * @param {string|Object} [options.body] Si c'est une string, elle doit être encodé en utf-8 et le header content-type doit être fourni (sinon c'est géré ici)
 * @param {Object} [options.headers] Les headers de la requête envoyée
 * @return {Promise<Object>} Résoud avec l'objet récupéré
 */
function fetchData (url, options) {
  return new Promise((resolve, reject) => {
    // lorsque le module http2 sera prod ready, on pourra éviter de jongler
    let httpModule
    if (/^https:\/\//.test(url)) httpModule = require('https')
    else if (/^http:\/\//.test(url)) httpModule = require('http')
    else return reject(Error(`url ${url} invalide`))
    const httpOpts = new URL(url)
    httpOpts.timeout = 1000 // en ms, pour établir la connexion
    httpOpts.method = options.method ? options.method.toUpperCase() : 'GET'
    httpOpts.headers = options.headers || {}
    httpOpts.headers.Accept = 'application/json'
    // apiKey
    if (options.apiToken) httpOpts.headers['X-ApiToken'] = encodeURIComponent(options.apiToken)
    // body
    const hasBody = Boolean(options.body)
    if (hasBody) {
      if (typeof options.body === 'object') {
        try {
          options.body = JSON.stringify(options.body)
          options.headers['Content-Type'] = 'application/json'
        } catch (error) {
          error.message = `options.body invalide : ${error.message}`
          return reject(error)
        }
      } else if (typeof options.body !== 'string') {
        return reject(Error(`body invalide (type ${typeof options.body})`))
      }
      // length donne le nb de caractères, pas le nb d'octets, faut passer par Buffer.byteLength
      httpOpts.headers['Content-Length'] = Buffer.byteLength(options.body, 'utf8')
    }

    // cf https://nodejs.org/dist/latest-v8.x/docs/api/http.html#http_http_request_options_callback
    const req = httpModule.request(httpOpts, (res) => {
      const statusCode = res.statusCode
      if (statusCode !== 200) return reject(Error(`${url} ne retourne pas 200 (${statusCode})`))
      const contentType = res.headers['content-type']
      if (!/^application\/json/.test(contentType)) return reject(Error(`${url} ne retourne pas de json`))
      res.setEncoding('utf8')
      let content = ''
      res.on('data', chunk => (content += chunk))
      res.on('end', () => {
        // l'objet qu'on va retourner
        let data
        try {
          data = JSON.parse(content)
        } catch (error) {
          console.error(error)
          return reject(Error(`${url} ne retourne pas de json valide (${error.toString()})`))
        }
        if (!data) return reject(Error(`${url} renvoie une réponse vide`))
        if (data.error) return reject(Error(`${url} retourne l’erreur ${data.error}`))
        resolve(data)
      })
    })
    req.on('error', reject)
    if (hasBody) req.write(options.body)
    req.end()
  })
}

/**
 * Récupère la ref d'une ressource privée
 * @param {string} rid
 * @param {string} apiToken
 * @return {Promise}
 */
function fetchPrivateRef (rid, apiToken) {
  if (!apiToken) return Promise.reject(Error(`Appel de fetchPrivateRef sans apiToken`))
  const url = getDataUrl(null, rid, { isPublic: false, isRef: true })
  return fetchData(url, { apiToken })
}

/**
 * Récupère une ressource privée
 * @param {string} rid
 * @param {string} apiToken
 * @return {Promise}
 */
function fetchPrivateRessource (rid, apiToken) {
  if (!apiToken) return Promise.reject(Error(`Appel de fetchPrivateRessource sans apiToken`))
  const url = getDataUrl(null, rid, { isPublic: false })
  return fetchData(url, { apiToken })
}

/**
 * Récupère la ref d'une ressource publique
 * @param {string} rid
 * @return {Promise}
 */
function fetchPublicRef (rid) {
  const url = getDataUrl(null, rid, { isPublic: true, isRef: true })
  return fetchData(url)
}

/**
 * Récupère une ressource publique
 * @param {string} rid
 * @return {Promise}
 */
function fetchPublicRessource (rid) {
  const url = getDataUrl(null, rid, { isPublic: true })
  return fetchData(url)
}

/**
 * Récupère la ref d'une ressource
 * @param {string} rid
 * @param {string} [apiToken]
 */
function fetchRef (rid, apiToken) {
  const url = getDataUrl(null, rid, { isRef: true, isPublic: !apiToken })
  return fetchData(url)
}

/**
 * Récupère une ressource
 * @param {string} rid
 * @param {string} [apiToken]
 */
function fetchRessource (rid, apiToken) {
  const url = getDataUrl(null, rid, { isPublic: !apiToken })
  return fetchData(url, { apiToken })
}

/**
 * Enregistre (ou modifie) une ressource
 * @param {string} baseId
 * @param {Ressource} ressource
 * @param {string} apiToken
 * @return {Promise}
 */
function saveRessource (baseId, ressource, apiToken) {
  if (!apiToken) return Promise.reject(Error(`Appel de saveRessource sans apiToken`))
  if (!baseId) return Promise.reject(Error(`Appel de saveRessource sans baseId`))
  const url = getBaseUrl(baseId) + 'api/ressource?format=ref'
  const options = {
    apiToken,
    method: 'post',
    body: ressource,
    headers: {
      'Content-Type': 'application/json'
    }
  }
  return fetchData(url, options)
}

module.exports = {
  fetchData,
  fetchPrivateRef,
  fetchPrivateRessource,
  fetchPublicRef,
  fetchPublicRessource,
  fetchRef,
  fetchRessource,
  saveRessource
}