/**
* 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
}