mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
238 lines
7.7 KiB
JavaScript
238 lines
7.7 KiB
JavaScript
/**
|
|
* API module.
|
|
* @module api
|
|
*/
|
|
|
|
import store from '@/store'
|
|
import { openWebSocket, getResponseData, handleError } from './handlers'
|
|
|
|
/**
|
|
* Options available for an API call.
|
|
*
|
|
* @typedef {Object} Options
|
|
* @property {Boolean} wait - If `true`, will display the waiting modal.
|
|
* @property {Boolean} websocket - if `true`, will open a websocket connection.
|
|
* @property {Boolean} initial - if `true` and an error occurs, the dismiss button will trigger a go back in history.
|
|
* @property {Boolean} asFormData - if `true`, will send the data with a body encoded as `"multipart/form-data"` instead of `"x-www-form-urlencoded"`).
|
|
*/
|
|
|
|
/**
|
|
* Representation of an API call for `api.fetchAll`
|
|
*
|
|
* @typedef {Array} Query
|
|
* @property {String} 0 - "method"
|
|
* @property {String|Object} 1 - "uri", uri to call as string or as an object for cached uris.
|
|
* @property {Object|null} 2 - "data"
|
|
* @property {Options} 3 - "options"
|
|
*/
|
|
|
|
/**
|
|
* Converts an object literal into an `URLSearchParams` that can be turned into a
|
|
* query string or used as a body in a `fetch` call.
|
|
*
|
|
* @param {Object} obj - An object literal to convert.
|
|
* @param {Object} options
|
|
* @param {Boolean} [options.addLocale=false] - Option to append the locale to the query string.
|
|
* @return {URLSearchParams}
|
|
*/
|
|
export function objectToParams(
|
|
obj,
|
|
{ addLocale = false } = {},
|
|
formData = false,
|
|
) {
|
|
const urlParams = formData ? new FormData() : new URLSearchParams()
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
if (Array.isArray(value)) {
|
|
value.forEach((v) => urlParams.append(key, v))
|
|
} else {
|
|
urlParams.append(key, value)
|
|
}
|
|
}
|
|
if (addLocale) {
|
|
urlParams.append('locale', store.getters.locale)
|
|
}
|
|
return urlParams
|
|
}
|
|
|
|
export default {
|
|
options: {
|
|
credentials: 'include',
|
|
mode: 'cors',
|
|
headers: {
|
|
// FIXME is it important to keep this previous `Accept` header ?
|
|
// 'Accept': 'application/json, text/javascript, */*; q=0.01',
|
|
// Auto header is :
|
|
// "Accept": "*/*",
|
|
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
},
|
|
|
|
/**
|
|
* Generic method to fetch the api without automatic response handling.
|
|
*
|
|
* @param {String} method - a method between 'GET', 'POST', 'PUT' and 'DELETE'.
|
|
* @param {String} uri
|
|
* @param {Object} [data={}] - data to send as body.
|
|
* @param {Options} [options={ wait = true, websocket = true, initial = false, asFormData = false }]
|
|
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
|
*/
|
|
async fetch(
|
|
method,
|
|
uri,
|
|
data = {},
|
|
humanKey = null,
|
|
{ wait = true, websocket = true, initial = false, asFormData = false } = {},
|
|
) {
|
|
// `await` because Vuex actions returns promises by default.
|
|
const request = await store.dispatch('INIT_REQUEST', {
|
|
method,
|
|
uri,
|
|
humanKey,
|
|
initial,
|
|
wait,
|
|
websocket,
|
|
})
|
|
|
|
if (websocket) {
|
|
await openWebSocket(request)
|
|
}
|
|
|
|
let options = this.options
|
|
if (method === 'GET') {
|
|
uri += `${uri.includes('?') ? '&' : '?'}locale=${store.getters.locale}`
|
|
} else {
|
|
options = {
|
|
...options,
|
|
method,
|
|
body: objectToParams(data, { addLocale: true }, true),
|
|
}
|
|
}
|
|
|
|
const response = await fetch('/yunohost/api/' + uri, options)
|
|
const responseData = await getResponseData(response)
|
|
store.dispatch('END_REQUEST', { request, success: response.ok, wait })
|
|
|
|
return response.ok
|
|
? responseData
|
|
: handleError(request, response, responseData)
|
|
},
|
|
|
|
/**
|
|
* Api multiple queries helper.
|
|
* Those calls will act as one (declare optional waiting for one but still create history entries for each)
|
|
* Calls are synchronous since the API can't handle multiple calls.
|
|
*
|
|
* @param {Array<Query>} queries - An array of queries with special representation.
|
|
* @param {Object} [options={}]
|
|
* @param {Boolean}
|
|
* @return {Promise<Array|Error>} Promise that resolve the api responses data or an error.
|
|
*/
|
|
async fetchAll(queries, { wait, initial } = {}) {
|
|
const results = []
|
|
if (wait) store.commit('SET_WAITING', true)
|
|
try {
|
|
for (const [method, uri, data, humanKey, options = {}] of queries) {
|
|
if (wait) options.wait = false
|
|
if (initial) options.initial = true
|
|
results.push(
|
|
await this[method.toLowerCase()](uri, data, humanKey, options),
|
|
)
|
|
}
|
|
} finally {
|
|
// Stop waiting even if there is an error.
|
|
if (wait) store.commit('SET_WAITING', false)
|
|
}
|
|
|
|
return results
|
|
},
|
|
|
|
/**
|
|
* Api get helper function.
|
|
*
|
|
* @param {String|Object} uri
|
|
* @param {null} [data=null] - for convenience in muliple calls, just pass null.
|
|
* @param {Options} [options={}] - options to apply to the call (default is `{ websocket: false, wait: false }`)
|
|
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
|
*/
|
|
get(uri, data = null, humanKey = null, options = {}) {
|
|
options = { websocket: false, wait: false, ...options }
|
|
if (typeof uri === 'string')
|
|
return this.fetch('GET', uri, null, humanKey, options)
|
|
return store.dispatch('GET', { ...uri, humanKey, options })
|
|
},
|
|
|
|
/**
|
|
* Api post helper function.
|
|
*
|
|
* @param {String|Object} uri
|
|
* @param {String} [data={}] - data to send as body.
|
|
* @param {Options} [options={}] - options to apply to the call
|
|
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
|
*/
|
|
post(uri, data = {}, humanKey = null, options = {}) {
|
|
if (typeof uri === 'string')
|
|
return this.fetch('POST', uri, data, humanKey, options)
|
|
return store.dispatch('POST', { ...uri, data, humanKey, options })
|
|
},
|
|
|
|
/**
|
|
* Api put helper function.
|
|
*
|
|
* @param {String|Object} uri
|
|
* @param {String} [data={}] - data to send as body.
|
|
* @param {Options} [options={}] - options to apply to the call
|
|
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
|
*/
|
|
put(uri, data = {}, humanKey = null, options = {}) {
|
|
if (typeof uri === 'string')
|
|
return this.fetch('PUT', uri, data, humanKey, options)
|
|
return store.dispatch('PUT', { ...uri, data, humanKey, options })
|
|
},
|
|
|
|
/**
|
|
* Api delete helper function.
|
|
*
|
|
* @param {String|Object} uri
|
|
* @param {String} [data={}] - data to send as body.
|
|
* @param {Options} [options={}] - options to apply to the call (default is `{ websocket: false, wait: false }`)
|
|
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
|
*/
|
|
delete(uri, data = {}, humanKey = null, options = {}) {
|
|
if (typeof uri === 'string')
|
|
return this.fetch('DELETE', uri, data, humanKey, options)
|
|
return store.dispatch('DELETE', { ...uri, data, humanKey, options })
|
|
},
|
|
|
|
/**
|
|
* Api reconnection helper. Resolve when server is reachable or fail after n attemps
|
|
*
|
|
* @param {Number} attemps - number of attemps before rejecting
|
|
* @param {Number} delay - delay between calls to the API in ms.
|
|
* @param {Number} initialDelay - delay before calling the API for the first time in ms.
|
|
* @return {Promise<undefined|Error>}
|
|
*/
|
|
tryToReconnect({ attemps = 5, delay = 2000, initialDelay = 0 } = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const api = this
|
|
|
|
function reconnect(n) {
|
|
api
|
|
.get('logout', {}, { key: 'reconnecting' })
|
|
.then(resolve)
|
|
.catch((err) => {
|
|
if (err.name === 'APIUnauthorizedError') {
|
|
resolve()
|
|
} else if (n < 1) {
|
|
reject(err)
|
|
} else {
|
|
setTimeout(() => reconnect(n - 1), delay)
|
|
}
|
|
})
|
|
}
|
|
|
|
if (initialDelay > 0) setTimeout(() => reconnect(attemps), initialDelay)
|
|
else reconnect(attemps)
|
|
})
|
|
},
|
|
}
|