'use strict'
/**
* On a besoin d'un seul objet qui liste les sesatheques pour toute l'appli (cliente ou serveur)
* Vu que le singleton en js est compliqué à utiliser, on passe par une var globale, avec getter et setters
* (on a besoin que plusieurs js incluant ce module mais compilés séparément utilisent la même instance)
*
*/
/**
* Ajoute un propriété en lecture seule à notre objet sesatheques
* @private
* @param name
* @param value
*/
function addProp (name, value) {
Object.defineProperty(sesatheques, name, {
__proto__: null,
enumerable: true,
configurable: false,
writable: false,
value: value
})
}
// eslint veut plus de hasOwnProperty appelée directement sur un objet (no-prototype-builtins)
const hasProp = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
const globalScope = (typeof global !== 'undefined') ? global : window
if (!globalScope.sesatheque) {
Object.defineProperty(globalScope, 'sesatheque', {
__proto__: null,
enumerable: true,
configurable: false,
writable: false,
value: {}
})
}
/**
* Gère les sésathèques connues (baseUrl liée à une baseId)
* C'est window.sesatheque.sesatheques dans un navigateur ou globalScope.sesatheque.sesatheques dans node
* au premier `import from 'sesatheque-client/src/sesatheques'` (es6)
* ou `require('sesatheque-client/dist/sesatheques')` (es5)
* @service sesatheques
* @memberOf globalScope.sesatheque
* @type {object}
*/
const sesatheques = {}
// on s'assure que cette fonction "génératrice" ne sera appelée qu'une seule fois,
// au premier import de ce module, les imports suivants auront une ref sur le même objet
if (!globalScope.sesatheque.sesatheques) {
/**
* La liste des sésathèques connues (fixées en dur ici) que l'on garde privée
* @private
* @type {{baseId:baseUrl}}
*/
const urls = {
sesabibli: 'https://bibliotheque.sesamath.net/',
sesacommun: 'https://commun.sesamath.net/'
// les baseId / baseUrl utilisées pour du dev local seront ajoutées au boot avec addSesatheque
// par chaque appli concernées, celles qui bossent ensemble devront simplement se coordonner
}
addProp('defaultBaseId', 'sesabibli')
addProp('defaultBaseUrl', urls.sesabibli)
/**
* Regexp pour tester une baseUrl valide (url absolue qui fini par un slash, slash seul accepté)
* @property reBaseUrl
* @memberOf sesatheques
* @type {RegExp}
*/
const reBaseUrl = /^(https?:\/\/[a-z0-9\-._]+(:[0-9]+)?(\/.*)?)?\/$/
addProp('reBaseUrl', reBaseUrl)
/**
* Ajoute une sesathèque à la liste courante
* @methodOf sesatheques
* @param {string} baseId
* @param {string} baseUrl avec slash de fin (url absolue ou démarrant par /)
* @returns {boolean} true si on l'a ajouté, false si ça existait déjà avec les même valeurs
* @throws {Error} Si paramètres incorrects
*/
const addSesatheque = (baseId, baseUrl) => {
if (!baseId || typeof baseId !== 'string') throw Error('baseId doit être une string non vide')
if (!baseUrl || typeof baseUrl !== 'string') throw Error('baseUrl doit être une string non vide')
// toujours un / de fin pour baseUrl
if (baseUrl.substr(-1) !== '/') {
console.error('baseUrl doit avoir un slash de fin, ajouté !')
baseUrl += '/'
}
// on refuse l'ajout d'une autre baseUrl pour une baseId existante
if (urls[baseId]) {
// existe déjà
if (baseUrl !== urls[baseId]) throw Error(`${baseId} est déjà définie avec une autre base ${urls[baseId]} (≠ ${baseUrl})`)
return false
}
// on refuse l'ajout d'une sésathèque existante sous un nouveau nom, pour garantir l'unicité des rid
const existingBaseId = getBaseId(baseUrl, null)
if (existingBaseId) {
if (baseId !== existingBaseId) throw Error(`${baseUrl} était déjà enregistré avec ${existingBaseId} (≠ ${baseId})`)
return false
}
if (sesatheques.reBaseUrl.test(baseUrl)) {
urls[baseId] = baseUrl
return true
}
}
addProp('addSesatheque', addSesatheque)
/**
* Ajoute une liste de sésathèques
* @param {{baseId: string, baseUrl: string[]}} list Tableau de {baseId, baseUrl}, certains éléments peuvent être des baseId connues
* @throws {Error} Si mauvais format ou url invalide
*/
const addSesatheques = (list) => {
if (!Array.isArray(list)) throw Error('Il faut passer un tableau dont chaque élément doit être un objet {baseId: string, baseUrl: string} ou une baseId')
list.forEach((s) => {
// on accepte dans la liste une baseId connue
if (typeof s === 'string') {
if (exists(s)) return
throw Error(`${s} n’est pas une sésathèque connue`)
}
if (!s.baseId) throw Error('sesatheque invalide (baseId manquante)')
if (!s.baseUrl) throw Error('sesatheque invalide (baseUrl manquante)')
addSesatheque(s.baseId, s.baseUrl)
})
}
addProp('addSesatheques', addSesatheques)
/**
* Retourne true si baseId est connue
* @memberOf sesatheques
* @param {string} baseId
* @return {boolean}
*/
const exists = (baseId) => hasProp(urls, baseId)
addProp('exists', exists)
/**
* Retourne la première baseId connue d'une baseUrl
* @memberOf sesatheques
* @param {string} baseUrl La baseUrl à chercher (mettre le slash de fin)
* @param {boolean} [strict=true] Passer false pour écrire en console et ne pas lancer d'exception si baseUrl n'existe pas, null pour rester silencieux
* @returns {string|undefined} La baseId si baseUrl était enregistrée,
* sinon undefined (si strict=false, sinon throw avant le return)
*/
const getBaseId = (baseUrl, strict = true) => {
for (const id in urls) {
if (urls[id] === baseUrl) return id
}
const errorMessage = baseUrl + ' n’est pas l’url d’une sesathèque connue'
if (strict) throw Error(errorMessage)
else if (strict !== null) console.error(errorMessage)
}
addProp('getBaseId', getBaseId)
/**
* Retourne la première baseId connue pour une ressource ou ref,
* si c'est un alias retourne la base de l'alias (pas celle de la ressource originale)
* @memberOf sesatheques
* @param {Ressource|Ref} ressource
* @param {boolean} [strict=true]
* @return {string}
* @throws si ressource sans rid ni aliasOf (ou strict && baseId inconnue)
*/
const getBaseIdFromRessource = (ressource, strict = true) => {
if (ressource.rid) return getBaseIdFromRid(ressource.rid, strict)
if (ressource.aliasOf) return getBaseIdFromRid(ressource.aliasOf, strict)
if (strict) {
console.error('ressource invalide', ressource)
throw Error('ressource sans rid ni aliasOf')
}
}
addProp('getBaseIdFromRessource', getBaseIdFromRessource)
/**
* Extrait baseId du rid (ressource Id) fourni, en lançant une exception en cas d'incohérence,
* peut donc être utilisé pour contrôler la validité d'un rid
* @memberOf sesatheques
* @param {string} rid
* @param {boolean} strict passer false pour récupérer undefined en cas de rid plausible mais base inconnue, passer null pour récupérer quand même la base inconnue.
* @return {string} La baseId extraite si elle est connue (undefined si !false && rid plausible et base inconnue)
* @throws {Error} Si rid n'est pas conforme (ou strict + base inconnue)
* - rid n'est pas une string
* - rid ne contient pas un seul slash
* - rid a son slash au début ou à la fin
* - si strict et que la base extraite est inconnue (avec strict à false ne lance pas d'exception et retourne undefined)
*/
const getBaseIdFromRid = (rid, strict = true) => {
if (typeof rid !== 'string') {
if (strict) throw new TypeError('rid n’est pas une string')
return
}
const pos = rid.indexOf('/')
if (pos < 1) throw Error(`rid ${rid} invalide (pas de baseId)`)
if (rid.length === pos) throw Error(`rid ${rid} invalide (pas d’id de ressource)`)
if (rid.indexOf('/', pos + 1) !== -1) throw Error(`rid ${rid} invalide (plusieurs slashes)`)
const baseId = rid.substr(0, pos)
if (exists(baseId)) return baseId
if (strict) throw Error(`baseId ${baseId} inconnue`)
if (strict === null) return baseId
}
addProp('getBaseIdFromRid', getBaseIdFromRid)
/**
* Retourne la baseId d'une url quelconque
* @param {string} url
* @param {boolean} strict passer false pour ne pas throw Error si l'url n'est pas sur une sesathèque connue
* @return {string}
*/
const getBaseIdFromUrlQcq = (url, strict = true) => {
if (typeof url !== 'string') throw new TypeError('url n’est pas une string')
for (const id in urls) {
if (url.indexOf(urls[id]) === 0) return id
}
if (strict) throw Error(`${url} n’est pas sur une sesatheque connue`)
}
addProp('getBaseIdFromUrlQcq', getBaseIdFromUrlQcq)
/**
* Retourne la baseUrl (avec slash de fin) d'une baseId connue, ou undefined
* @memberOf sesatheques
* @param {string} baseId
* @param {boolean} [strict=true] Passer false pour ne pas lancer d'exception si baseId n'existe pas
* @returns {string|undefined}
* @throws {Error} Si strict=true et baseId inconnue
*/
const getBaseUrl = (baseId, strict = true) => {
if (urls[baseId]) return urls[baseId]
const errorMsg = `${baseId} n’est pas un identifiant de sesathèque connu`
if (strict) throw Error(errorMsg)
}
addProp('getBaseUrl', getBaseUrl)
/**
* Découpe id en deux morceau d'après le 1er slash
* @memberOf sesatheques
* @param {string} id
* @return {Array} le tableau [début, fin] découpé au 1er slash (début vaut undefined et fin vaut id si id ne contient pas de slash, id sera undefined aussi si ce n'était ni un nombre ni une string non vide)
*/
const getComponents = (id) => {
if (!id) return [undefined, undefined]
if (typeof id === 'number') id = String(id) // seul cast qu'on permet
if (typeof id !== 'string') return [undefined, undefined]
const slashPos = id.indexOf('/')
if (slashPos > 0 && slashPos < id.length) {
return [id.substr(0, slashPos), id.substr(slashPos + 1)]
} else {
return [undefined, id]
}
}
addProp('getComponents', getComponents)
/**
* Retourne la liste de toutes les sésathèques connues, sous la forme {baseId1:baseUrl1, …}
* @memberOf sesatheques
* @return {{baseId:baseUrl}}
*/
const getList = () => {
// clonage du fainéant
return Object.assign({}, urls)
}
addProp('getList', getList)
/**
* Retourne un [baseId, id] extrait de rid et lance une erreur si baseId est inconnue ou id est vide ou avec slash
* @memberOf sesatheques
* @param {string} rid un rid ou aliasOf
* @return {string[]} deux éléments, baseId et oid
* @throws si rid est malformé ou base inconnue
*/
const getRidComponents = (rid) => {
if (typeof rid !== 'string') throw new TypeError('rid n’est pas une string')
const [baseId, oid] = getComponents(rid)
if (!exists(baseId)) throw Error(`${baseId} n’est pas une sesathèque connue`)
if (oid.indexOf('/') !== -1) throw Error(`rid ${rid} invalide (id contient un slash)`)
if (!oid) throw Error('rid invalide (id vide)')
return [baseId, oid]
}
addProp('getRidComponents', getRidComponents)
// on définie une propriété qui ne contient rien d'autre que des méthodes
Object.defineProperty(globalScope.sesatheque, 'sesatheques', {
__proto__: null,
enumerable: true,
configurable: false,
writable: false,
value: sesatheques
})
}
export default globalScope.sesatheque.sesatheques