mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
ts: add api modules typing
This commit is contained in:
parent
7c6092e6dc
commit
cdaf8a7bcb
4 changed files with 275 additions and 132 deletions
|
@ -4,41 +4,82 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import { openWebSocket, getResponseData, handleError } from './handlers'
|
import { openWebSocket, getResponseData, getError } from './handlers'
|
||||||
|
import type { Obj } from '@/types/commons'
|
||||||
|
|
||||||
/**
|
export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||||
* 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"`).
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
type StoreUri = {
|
||||||
* Representation of an API call for `api.fetchAll`
|
uri: string
|
||||||
*
|
storeKey?: string
|
||||||
* @typedef {Array} Query
|
param?: string
|
||||||
* @property {String} 0 - "method"
|
}
|
||||||
* @property {String|Object} 1 - "uri", uri to call as string or as an object for cached uris.
|
type HumanKey = {
|
||||||
* @property {Object|null} 2 - "data"
|
key: string
|
||||||
* @property {Options} 3 - "options"
|
[propName: string]: any
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
type APIQueryOptions = {
|
||||||
|
// Display the waiting modal
|
||||||
|
wait?: boolean
|
||||||
|
// Open a websocket connection
|
||||||
|
websocket?: boolean
|
||||||
|
// If an error occurs, the dismiss button will trigger a go back in history
|
||||||
|
initial?: boolean
|
||||||
|
// Send the data with a body encoded as `"multipart/form-data"` instead of `"x-www-form-urlencoded"`)
|
||||||
|
asFormData?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type APIQuery = [
|
||||||
|
method: RequestMethod,
|
||||||
|
uri: string | StoreUri,
|
||||||
|
data?: Obj | null,
|
||||||
|
humanKey?: string | HumanKey | null,
|
||||||
|
options?: APIQueryOptions,
|
||||||
|
]
|
||||||
|
|
||||||
|
export type APIErrorData = {
|
||||||
|
error: string
|
||||||
|
error_key?: string
|
||||||
|
log_ref?: string
|
||||||
|
traceback?: string
|
||||||
|
name?: string // FIXME name is field id right?
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestStatus = 'pending' | 'success' | 'warning' | 'error'
|
||||||
|
|
||||||
|
export type APIRequest = {
|
||||||
|
method: RequestMethod
|
||||||
|
uri: string
|
||||||
|
humanRouteKey: HumanKey['key']
|
||||||
|
humanRoute: string
|
||||||
|
initial: APIQueryOptions['initial']
|
||||||
|
status: RequestStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsocketMessage = {
|
||||||
|
text: string
|
||||||
|
status: 'info' | 'success' | 'warning' | 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type APIRequestAction = APIRequest & {
|
||||||
|
messages: WebsocketMessage[]
|
||||||
|
date: number
|
||||||
|
warning: number
|
||||||
|
errors: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an object literal into an `URLSearchParams` that can be turned into a
|
* Converts an object literal into an `URLSearchParams` that can be turned into a
|
||||||
* query string or used as a body in a `fetch` call.
|
* query string or used as a body in a `fetch` call.
|
||||||
*
|
*
|
||||||
* @param {Object} obj - An object literal to convert.
|
* @param obj - An object literal to convert to `FormData` or `URLSearchParams`
|
||||||
* @param {Object} options
|
* @param addLocale - Append the locale to the returned object
|
||||||
* @param {Boolean} [options.addLocale=false] - Option to append the locale to the query string.
|
* @param formData - Returns a `FormData` instead of `URLSearchParams`
|
||||||
* @return {URLSearchParams}
|
|
||||||
*/
|
*/
|
||||||
export function objectToParams(
|
export function objectToParams(
|
||||||
obj,
|
obj: Obj,
|
||||||
{ addLocale = false } = {},
|
{ addLocale = false, formData = false } = {},
|
||||||
formData = false,
|
|
||||||
) {
|
) {
|
||||||
const urlParams = formData ? new FormData() : new URLSearchParams()
|
const urlParams = formData ? new FormData() : new URLSearchParams()
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
@ -66,36 +107,46 @@ export default {
|
||||||
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
},
|
},
|
||||||
},
|
} as RequestInit,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic method to fetch the api without automatic response handling.
|
* Generic method to fetch the api.
|
||||||
*
|
*
|
||||||
* @param {String} method - a method between 'GET', 'POST', 'PUT' and 'DELETE'.
|
* @param method - a method in `'GET' | 'POST' | 'PUT' | 'DELETE'`
|
||||||
* @param {String} uri
|
* @param uri - URI to fetch
|
||||||
* @param {Object} [data={}] - data to send as body.
|
* @param data - Data to send as body
|
||||||
* @param {Options} [options={ wait = true, websocket = true, initial = false, asFormData = false }]
|
* @param options - {@link APIQueryOptions}
|
||||||
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
|
||||||
|
* @returns Promise that resolve the api response data
|
||||||
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
async fetch(
|
async fetch(
|
||||||
method,
|
method: RequestMethod,
|
||||||
uri,
|
uri: string,
|
||||||
data = {},
|
data: Obj | null | undefined = {},
|
||||||
humanKey = null,
|
humanKey: string | HumanKey | null = null,
|
||||||
{ wait = true, websocket = true, initial = false, asFormData = false } = {},
|
{
|
||||||
) {
|
wait = true,
|
||||||
|
websocket = true,
|
||||||
|
initial = false,
|
||||||
|
asFormData = true,
|
||||||
|
}: APIQueryOptions = {},
|
||||||
|
): Promise<Obj | string> {
|
||||||
// `await` because Vuex actions returns promises by default.
|
// `await` because Vuex actions returns promises by default.
|
||||||
const request = await store.dispatch('INIT_REQUEST', {
|
const request: APIRequest | APIRequestAction = await store.dispatch(
|
||||||
method,
|
'INIT_REQUEST',
|
||||||
uri,
|
{
|
||||||
humanKey,
|
method,
|
||||||
initial,
|
uri,
|
||||||
wait,
|
humanKey,
|
||||||
websocket,
|
initial,
|
||||||
})
|
wait,
|
||||||
|
websocket,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if (websocket) {
|
if (websocket) {
|
||||||
await openWebSocket(request)
|
await openWebSocket(request as APIRequestAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = this.options
|
let options = this.options
|
||||||
|
@ -105,7 +156,9 @@ export default {
|
||||||
options = {
|
options = {
|
||||||
...options,
|
...options,
|
||||||
method,
|
method,
|
||||||
body: objectToParams(data, { addLocale: true }, true),
|
body: data
|
||||||
|
? objectToParams(data, { addLocale: true, formData: asFormData })
|
||||||
|
: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,9 +166,11 @@ export default {
|
||||||
const responseData = await getResponseData(response)
|
const responseData = await getResponseData(response)
|
||||||
store.dispatch('END_REQUEST', { request, success: response.ok, wait })
|
store.dispatch('END_REQUEST', { request, success: response.ok, wait })
|
||||||
|
|
||||||
return response.ok
|
if (!response.ok) {
|
||||||
? responseData
|
throw getError(request, response, responseData as string | APIErrorData)
|
||||||
: handleError(request, response, responseData)
|
}
|
||||||
|
|
||||||
|
return responseData
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,20 +178,26 @@ export default {
|
||||||
* Those calls will act as one (declare optional waiting for one but still create history entries for each)
|
* 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.
|
* Calls are synchronous since the API can't handle multiple calls.
|
||||||
*
|
*
|
||||||
* @param {Array<Query>} queries - An array of queries with special representation.
|
* @param queries - Array of {@link APIQuery}
|
||||||
* @param {Object} [options={}]
|
* @param wait - Show the waiting modal until every queries have been resolved
|
||||||
* @param {Boolean}
|
* @param initial - Inform that thoses queries are required for a view to be displayed
|
||||||
* @return {Promise<Array|Error>} Promise that resolve the api responses data or an error.
|
* @returns Promise that resolves an array of server responses
|
||||||
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
async fetchAll(queries, { wait, initial } = {}) {
|
async fetchAll(queries: APIQuery[], { wait = false, initial = false } = {}) {
|
||||||
const results = []
|
const results: Array<Obj | string> = []
|
||||||
if (wait) store.commit('SET_WAITING', true)
|
if (wait) store.commit('SET_WAITING', true)
|
||||||
try {
|
try {
|
||||||
for (const [method, uri, data, humanKey, options = {}] of queries) {
|
for (const [method, uri, data, humanKey, options = {}] of queries) {
|
||||||
if (wait) options.wait = false
|
if (wait) options.wait = false
|
||||||
if (initial) options.initial = true
|
if (initial) options.initial = true
|
||||||
results.push(
|
results.push(
|
||||||
await this[method.toLowerCase()](uri, data, humanKey, options),
|
await this[method.toLowerCase() as 'get' | 'post' | 'put' | 'delete'](
|
||||||
|
uri,
|
||||||
|
data,
|
||||||
|
humanKey,
|
||||||
|
options,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -150,27 +211,41 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Api get helper function.
|
* Api get helper function.
|
||||||
*
|
*
|
||||||
* @param {String|Object} uri
|
* @param uri - uri to fetch
|
||||||
* @param {null} [data=null] - for convenience in muliple calls, just pass null.
|
* @param data - for convenience in muliple calls, just pass null
|
||||||
* @param {Options} [options={}] - options to apply to the call (default is `{ websocket: false, wait: false }`)
|
* @param humanKey - key and eventually some data to build the query's description
|
||||||
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
* @param options - {@link APIQueryOptions}
|
||||||
|
* @returns Promise that resolve the api response data or an error
|
||||||
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
get(uri, data = null, humanKey = null, options = {}) {
|
get(
|
||||||
|
uri: string | StoreUri,
|
||||||
|
data: Obj | null = null,
|
||||||
|
humanKey: string | HumanKey | null = null,
|
||||||
|
options: APIQueryOptions = {},
|
||||||
|
): Promise<Obj | string> {
|
||||||
options = { websocket: false, wait: false, ...options }
|
options = { websocket: false, wait: false, ...options }
|
||||||
if (typeof uri === 'string')
|
if (typeof uri === 'string')
|
||||||
return this.fetch('GET', uri, null, humanKey, options)
|
return this.fetch('GET', uri, data, humanKey, options)
|
||||||
return store.dispatch('GET', { ...uri, humanKey, options })
|
return store.dispatch('GET', { ...uri, humanKey, options })
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Api post helper function.
|
* Api post helper function.
|
||||||
*
|
*
|
||||||
* @param {String|Object} uri
|
* @param uri - uri to fetch
|
||||||
* @param {String} [data={}] - data to send as body.
|
* @param data - data to send as body
|
||||||
* @param {Options} [options={}] - options to apply to the call
|
* @param humanKey - key and eventually some data to build the query's description
|
||||||
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
* @param options - {@link APIQueryOptions}
|
||||||
|
* @returns Promise that resolve the api response data or an error
|
||||||
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
post(uri, data = {}, humanKey = null, options = {}) {
|
post(
|
||||||
|
uri: string | StoreUri,
|
||||||
|
data: Obj | null | undefined = {},
|
||||||
|
humanKey: string | HumanKey | null = null,
|
||||||
|
options: APIQueryOptions = {},
|
||||||
|
): Promise<Obj | string> {
|
||||||
if (typeof uri === 'string')
|
if (typeof uri === 'string')
|
||||||
return this.fetch('POST', uri, data, humanKey, options)
|
return this.fetch('POST', uri, data, humanKey, options)
|
||||||
return store.dispatch('POST', { ...uri, data, humanKey, options })
|
return store.dispatch('POST', { ...uri, data, humanKey, options })
|
||||||
|
@ -179,12 +254,19 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Api put helper function.
|
* Api put helper function.
|
||||||
*
|
*
|
||||||
* @param {String|Object} uri
|
* @param uri - uri to fetch
|
||||||
* @param {String} [data={}] - data to send as body.
|
* @param data - data to send as body
|
||||||
* @param {Options} [options={}] - options to apply to the call
|
* @param humanKey - key and eventually some data to build the query's description
|
||||||
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
* @param options - {@link APIQueryOptions}
|
||||||
|
* @returns Promise that resolve the api response data or an error
|
||||||
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
put(uri, data = {}, humanKey = null, options = {}) {
|
put(
|
||||||
|
uri: string | StoreUri,
|
||||||
|
data: Obj | null | undefined = {},
|
||||||
|
humanKey: string | HumanKey | null = null,
|
||||||
|
options: APIQueryOptions = {},
|
||||||
|
): Promise<Obj | string> {
|
||||||
if (typeof uri === 'string')
|
if (typeof uri === 'string')
|
||||||
return this.fetch('PUT', uri, data, humanKey, options)
|
return this.fetch('PUT', uri, data, humanKey, options)
|
||||||
return store.dispatch('PUT', { ...uri, data, humanKey, options })
|
return store.dispatch('PUT', { ...uri, data, humanKey, options })
|
||||||
|
@ -193,12 +275,19 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Api delete helper function.
|
* Api delete helper function.
|
||||||
*
|
*
|
||||||
* @param {String|Object} uri
|
* @param uri - uri to fetch
|
||||||
* @param {String} [data={}] - data to send as body.
|
* @param data - data to send as body
|
||||||
* @param {Options} [options={}] - options to apply to the call (default is `{ websocket: false, wait: false }`)
|
* @param humanKey - key and eventually some data to build the query's description
|
||||||
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
|
* @param options - {@link APIQueryOptions}
|
||||||
|
* @returns Promise that resolve the api response data or an error
|
||||||
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
delete(uri, data = {}, humanKey = null, options = {}) {
|
delete(
|
||||||
|
uri: string | StoreUri,
|
||||||
|
data: Obj | null | undefined = {},
|
||||||
|
humanKey: string | HumanKey | null = null,
|
||||||
|
options: APIQueryOptions = {},
|
||||||
|
): Promise<Obj | string> {
|
||||||
if (typeof uri === 'string')
|
if (typeof uri === 'string')
|
||||||
return this.fetch('DELETE', uri, data, humanKey, options)
|
return this.fetch('DELETE', uri, data, humanKey, options)
|
||||||
return store.dispatch('DELETE', { ...uri, data, humanKey, options })
|
return store.dispatch('DELETE', { ...uri, data, humanKey, options })
|
||||||
|
@ -207,16 +296,15 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Api reconnection helper. Resolve when server is reachable or fail after n attemps
|
* Api reconnection helper. Resolve when server is reachable or fail after n attemps
|
||||||
*
|
*
|
||||||
* @param {Number} attemps - number of attemps before rejecting
|
* @param attemps - Number of attemps before rejecting
|
||||||
* @param {Number} delay - delay between calls to the API in ms.
|
* @param delay - Delay between calls to the API in ms
|
||||||
* @param {Number} initialDelay - delay before calling the API for the first time in ms.
|
* @param initialDelay - Delay before calling the API for the first time in ms
|
||||||
* @return {Promise<undefined|Error>}
|
* @returns Promise that resolve yunohost version infos
|
||||||
|
* @throws Throw an `APIError` or subclass depending on server response
|
||||||
*/
|
*/
|
||||||
tryToReconnect({ attemps = 5, delay = 2000, initialDelay = 0 } = {}) {
|
tryToReconnect({ attemps = 5, delay = 2000, initialDelay = 0 } = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const api = this
|
function reconnect(n: number) {
|
||||||
|
|
||||||
function reconnect(n) {
|
|
||||||
store
|
store
|
||||||
.dispatch('GET_YUNOHOST_INFOS')
|
.dispatch('GET_YUNOHOST_INFOS')
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
|
|
|
@ -4,16 +4,27 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import i18n from '@/i18n'
|
import i18n from '@/i18n'
|
||||||
|
import type { APIErrorData, RequestMethod, APIRequest } from './api'
|
||||||
|
|
||||||
class APIError extends Error {
|
class APIError extends Error {
|
||||||
constructor(request, { url, status, statusText }, { error }) {
|
name = 'APIError'
|
||||||
|
code: number
|
||||||
|
status: string
|
||||||
|
method: RequestMethod
|
||||||
|
request: APIRequest
|
||||||
|
path: string
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
{ url, status, statusText }: Response,
|
||||||
|
{ error }: APIErrorData,
|
||||||
|
) {
|
||||||
super(
|
super(
|
||||||
error
|
error
|
||||||
? error.replaceAll('\n', '<br>')
|
? error.replaceAll('\n', '<br>')
|
||||||
: i18n.global.t('error_server_unexpected'),
|
: i18n.global.t('error_server_unexpected'),
|
||||||
)
|
)
|
||||||
const urlObj = new URL(url)
|
const urlObj = new URL(url)
|
||||||
this.name = 'APIError'
|
|
||||||
this.code = status
|
this.code = status
|
||||||
this.status = statusText
|
this.status = statusText
|
||||||
this.method = request.method
|
this.method = request.method
|
||||||
|
@ -23,76 +34,114 @@ class APIError extends Error {
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
/* eslint-disable-next-line */
|
/* eslint-disable-next-line */
|
||||||
console.error(`${this.name} (${this.code}): ${this.uri}\n${this.message}`)
|
console.error(`${this.name} (${this.code}): ${this.path}\n${this.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log (Special error to trigger a redirect to a log page)
|
// Log (Special error to trigger a redirect to a log page)
|
||||||
class APIErrorLog extends APIError {
|
class APIErrorLog extends APIError {
|
||||||
constructor(method, response, errorData) {
|
name = 'APIErrorLog'
|
||||||
super(method, response, errorData)
|
logRef: string
|
||||||
this.logRef = errorData.log_ref
|
|
||||||
this.name = 'APIErrorLog'
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
errorData: APIErrorData,
|
||||||
|
) {
|
||||||
|
super(request, response, errorData)
|
||||||
|
this.logRef = errorData.log_ref as string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0 — (means "the connexion has been closed" apparently)
|
// 0 — (means "the connexion has been closed" apparently)
|
||||||
class APIConnexionError extends APIError {
|
class APIConnexionError extends APIError {
|
||||||
constructor(method, response) {
|
name = 'APIConnexionError'
|
||||||
super(method, response, {
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
_errorData: APIErrorData,
|
||||||
|
) {
|
||||||
|
super(request, response, {
|
||||||
error: i18n.global.t('error_connection_interrupted'),
|
error: i18n.global.t('error_connection_interrupted'),
|
||||||
})
|
})
|
||||||
this.name = 'APIConnexionError'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 400 — Bad Request
|
// 400 — Bad Request
|
||||||
class APIBadRequestError extends APIError {
|
class APIBadRequestError extends APIError {
|
||||||
constructor(method, response, errorData) {
|
name = 'APIBadRequestError'
|
||||||
super(method, response, errorData)
|
key: string
|
||||||
this.name = 'APIBadRequestError'
|
data: APIErrorData
|
||||||
this.key = errorData.error_key
|
|
||||||
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
errorData: APIErrorData,
|
||||||
|
) {
|
||||||
|
super(request, response, errorData)
|
||||||
|
this.key = errorData.error_key as string
|
||||||
this.data = errorData
|
this.data = errorData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 401 — Unauthorized
|
// 401 — Unauthorized
|
||||||
class APIUnauthorizedError extends APIError {
|
class APIUnauthorizedError extends APIError {
|
||||||
constructor(method, response, errorData) {
|
name = 'APIUnauthorizedError'
|
||||||
super(method, response, { error: i18n.global.t('unauthorized') })
|
|
||||||
this.name = 'APIUnauthorizedError'
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
_errorData: APIErrorData,
|
||||||
|
) {
|
||||||
|
super(request, response, { error: i18n.global.t('unauthorized') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 404 — Not Found
|
// 404 — Not Found
|
||||||
class APINotFoundError extends APIError {
|
class APINotFoundError extends APIError {
|
||||||
constructor(method, response, errorData) {
|
name = 'APINotFoundError'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
errorData: APIErrorData,
|
||||||
|
) {
|
||||||
errorData.error = i18n.global.t('api_not_found')
|
errorData.error = i18n.global.t('api_not_found')
|
||||||
super(method, response, errorData)
|
super(request, response, errorData)
|
||||||
this.name = 'APINotFoundError'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 500 — Server Internal Error
|
// 500 — Server Internal Error
|
||||||
class APIInternalError extends APIError {
|
class APIInternalError extends APIError {
|
||||||
constructor(method, response, errorData) {
|
name = 'APIInternalError'
|
||||||
super(method, response, errorData)
|
traceback: string | null
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
errorData: APIErrorData,
|
||||||
|
) {
|
||||||
|
super(request, response, errorData)
|
||||||
this.traceback = errorData.traceback || null
|
this.traceback = errorData.traceback || null
|
||||||
this.name = 'APIInternalError'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 502 — Bad gateway (means API is down)
|
// 502 — Bad gateway (means API is down)
|
||||||
class APINotRespondingError extends APIError {
|
class APINotRespondingError extends APIError {
|
||||||
constructor(method, response) {
|
name = 'APINotRespondingError'
|
||||||
super(method, response, { error: i18n.global.t('api_not_responding') })
|
|
||||||
this.name = 'APINotRespondingError'
|
constructor(
|
||||||
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
_errorData: APIErrorData,
|
||||||
|
) {
|
||||||
|
super(request, response, { error: i18n.global.t('api_not_responding') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temp factory
|
// Temp factory
|
||||||
const errors = {
|
const errors = {
|
||||||
[undefined]: APIError,
|
default: APIError,
|
||||||
log: APIErrorLog,
|
log: APIErrorLog,
|
||||||
0: APIConnexionError,
|
0: APIConnexionError,
|
||||||
400: APIBadRequestError,
|
400: APIBadRequestError,
|
||||||
|
|
|
@ -5,18 +5,20 @@
|
||||||
|
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import errors, { APIError } from './errors'
|
import errors, { APIError } from './errors'
|
||||||
|
import type { Obj } from '@/types/commons'
|
||||||
|
import type { APIErrorData, APIRequest, APIRequestAction } from './api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to get response content as json and if it's not as text.
|
* Try to get response content as json and if it's not as text.
|
||||||
*
|
*
|
||||||
* @param {Response} response - A fetch `Response` object.
|
* @param response - A fetch `Response` object.
|
||||||
* @return {(Object|String)} Parsed response's json or response's text.
|
* @returns Parsed response's json or response's text.
|
||||||
*/
|
*/
|
||||||
export async function getResponseData(response) {
|
export async function getResponseData(response: Response) {
|
||||||
// FIXME the api should always return json as response
|
// FIXME the api should always return json as response
|
||||||
const responseText = await response.text()
|
const responseText = await response.text()
|
||||||
try {
|
try {
|
||||||
return JSON.parse(responseText)
|
return JSON.parse(responseText) as Obj
|
||||||
} catch {
|
} catch {
|
||||||
return responseText
|
return responseText
|
||||||
}
|
}
|
||||||
|
@ -28,10 +30,10 @@ export async function getResponseData(response) {
|
||||||
* we have to open it for every calls.
|
* we have to open it for every calls.
|
||||||
* Messages are dispatch to the store so it can handle them.
|
* Messages are dispatch to the store so it can handle them.
|
||||||
*
|
*
|
||||||
* @param {Object} request - Request info data.
|
* @param request - Request info data.
|
||||||
* @return {Promise<Event>} Promise that resolve on websocket 'open' or 'error' event.
|
* @returns Promise that resolve on websocket 'open' or 'error' event.
|
||||||
*/
|
*/
|
||||||
export function openWebSocket(request) {
|
export function openWebSocket(request: APIRequestAction): Promise<Event> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const ws = new WebSocket(
|
const ws = new WebSocket(
|
||||||
`wss://${store.getters.host}/yunohost/api/messages`,
|
`wss://${store.getters.host}/yunohost/api/messages`,
|
||||||
|
@ -52,13 +54,19 @@ export function openWebSocket(request) {
|
||||||
/**
|
/**
|
||||||
* Handler for API errors.
|
* Handler for API errors.
|
||||||
*
|
*
|
||||||
* @param {Object} request - Request info data.
|
* @param request - Request info data.
|
||||||
* @param {Response} response - A consumed fetch `Response` object.
|
* @param response - A consumed fetch `Response` object.
|
||||||
* @param {Object|String} errorData - The response parsed json/text.
|
* @param errorData - The response parsed json/text.
|
||||||
* @throws Will throw a `APIError` with request and response data.
|
* @returns an `APIError` or subclass with request and response data.
|
||||||
*/
|
*/
|
||||||
export async function handleError(request, response, errorData) {
|
export function getError(
|
||||||
let errorCode = response.status in errors ? response.status : undefined
|
request: APIRequest,
|
||||||
|
response: Response,
|
||||||
|
errorData: string | APIErrorData,
|
||||||
|
) {
|
||||||
|
let errorCode = (
|
||||||
|
response.status in errors ? response.status : 'default'
|
||||||
|
) as keyof typeof errors
|
||||||
if (typeof errorData === 'string') {
|
if (typeof errorData === 'string') {
|
||||||
// FIXME API: Patching errors that are plain text or html.
|
// FIXME API: Patching errors that are plain text or html.
|
||||||
errorData = { error: errorData }
|
errorData = { error: errorData }
|
||||||
|
@ -69,16 +77,14 @@ export async function handleError(request, response, errorData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This error can be catched by a view otherwise it will be catched by the `onUnhandledAPIError` handler.
|
// This error can be catched by a view otherwise it will be catched by the `onUnhandledAPIError` handler.
|
||||||
throw new errors[errorCode](request, response, errorData)
|
return new errors[errorCode](request, response, errorData)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If an APIError is not catched by a view it will be dispatched to the store so the
|
* If an APIError is not catched by a view it will be dispatched to the store so the
|
||||||
* error can be displayed in the error modal.
|
* error can be displayed in the error modal.
|
||||||
*
|
|
||||||
* @param {APIError} error
|
|
||||||
*/
|
*/
|
||||||
export function onUnhandledAPIError(error) {
|
export function onUnhandledAPIError(error: APIError) {
|
||||||
error.log()
|
error.log()
|
||||||
store.dispatch('HANDLE_ERROR', error)
|
store.dispatch('HANDLE_ERROR', error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export { default, objectToParams } from './api'
|
export { default, objectToParams } from './api'
|
||||||
export { handleError, registerGlobalErrorHandlers } from './handlers'
|
export { getError, registerGlobalErrorHandlers } from './handlers'
|
||||||
|
|
Loading…
Reference in a new issue