mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-01 12:19:28 +00:00
561 lines
15 KiB
JavaScript
561 lines
15 KiB
JavaScript
import FhcAlert from './FhcAlert.js';
|
|
|
|
|
|
export default {
|
|
install: (app, options) => {
|
|
if (app.config.globalProperties.$api) {
|
|
return;
|
|
}
|
|
|
|
if (!app.config.globalProperties.$fhcAlert)
|
|
app.use(FhcAlert);
|
|
|
|
function _send_array_or_object(errors, func) {
|
|
if (Array.isArray(errors))
|
|
errors.forEach(func);
|
|
else
|
|
Object.entries(errors).forEach(
|
|
([title, errs]) => errs.forEach(
|
|
error => func(error, title)
|
|
)
|
|
);
|
|
}
|
|
let DEFAULT_ERROR_CONFIG = {
|
|
success: true,
|
|
fail: true,
|
|
combine: {
|
|
form: ['validation', 'general'],
|
|
toast: ['validation', 'general', 'not_found', 'site_failed']
|
|
},
|
|
handler: {
|
|
form(form, errors) {
|
|
form.clearValidation();
|
|
errors.forEach(err => form.setFeedback(
|
|
false,
|
|
err.messages || err.message
|
|
));
|
|
},
|
|
async toast(errors) {
|
|
const $p = app.config.globalProperties.$p;
|
|
if (!$p)
|
|
return Promise.reject('Phrasen plugin not loaded!');
|
|
|
|
async function _format_toast(errors) {
|
|
errors = errors.reduce((result, err) => {
|
|
switch (err.type) {
|
|
case 'not_found':
|
|
case 'site_failed':
|
|
if (err.message)
|
|
result[err.message] = [err.url];
|
|
else
|
|
result._default = [err.url];
|
|
break;
|
|
case 'general':
|
|
if (!result._default)
|
|
result._default = [];
|
|
result._default.push(err.message);
|
|
break;
|
|
case 'validation':
|
|
Object.entries(err.messages)
|
|
.forEach(([field, msg]) => {
|
|
if (!result[field])
|
|
result[field] = [];
|
|
if (Array.isArray(msg))
|
|
result[field].push(...msg);
|
|
else
|
|
result[field].push(msg);
|
|
});
|
|
break;
|
|
}
|
|
return result;
|
|
}, {});
|
|
let counter = 0;
|
|
const msgs = await Promise.all(Object.entries(errors)
|
|
.sort((a, b) => ['_default'].indexOf(b[0]) - ['_default'].indexOf(a[0])) // sort _default first
|
|
.map(async ([field, msgs]) => {
|
|
if (field == '_default') {
|
|
await $p.loadCategory('dashboard');
|
|
const general = $p.t('dashboard/general');
|
|
field = '<dt class="d-none">' + general + '</dt>';
|
|
} else {
|
|
field = '<dt>' + field + '</dt>';
|
|
}
|
|
counter += msgs.length;
|
|
return field
|
|
+ '<dd>'
|
|
+ msgs.join('</dd><dd>')
|
|
+ '</dd>';
|
|
}));
|
|
return {
|
|
counter,
|
|
msgs
|
|
}
|
|
}
|
|
|
|
let counter, msgs;
|
|
if (Array.isArray(errors)) {
|
|
({ counter, msgs } = await _format_toast(errors));
|
|
} else {
|
|
({ counter, msgs } = await Object.entries(errors)
|
|
.reduce(async (res, [title, errs]) => {
|
|
const result = await res;
|
|
const { counter, msgs } = await _format_toast(errs);
|
|
result.counter += counter;
|
|
result.msgs.push('<dt>'
|
|
+ title
|
|
+ '</dt><dd><dl>'
|
|
+ msgs.join('')
|
|
+ '</dl></dd>');
|
|
return result;
|
|
}, Promise.resolve({ counter: 0, msgs: []})));
|
|
}
|
|
|
|
await $p.loadCategory('ui');
|
|
const n_errors = $p.t('ui/n_errors', { n: counter });
|
|
|
|
app.config.globalProperties.$fhcAlert.alertDefault(
|
|
'error',
|
|
n_errors,
|
|
'<dl>' + msgs.join('') + '</dl>',
|
|
true,
|
|
true
|
|
);
|
|
},
|
|
php(errors) {
|
|
_send_array_or_object(errors, (error, title) => {
|
|
var message = '';
|
|
message += 'Message: ' + error.message + '\n\n';
|
|
message += 'Filename: ' + error.filename + '\n';
|
|
message += 'Line Number: ' + error.line + '\n';
|
|
if (error.backtrace && error.backtrace.length) {
|
|
message += '\nBacktrace: ';
|
|
error.backtrace.forEach(err => {
|
|
message += '\n\tFile: ' + err.file + '\n';
|
|
message += '\tLine: ' + err.line + '\n';
|
|
message += '\tFunction: ' + err.function + '\n';
|
|
});
|
|
}
|
|
switch (error.severity) {
|
|
case 'Warning':
|
|
case 'Core Warning':
|
|
case 'Compile Warning':
|
|
case 'User Warning':
|
|
if (title)
|
|
title += ': PHP ' + error.severity;
|
|
else
|
|
title = 'PHP ' + error.severity;
|
|
app.config.globalProperties.$fhcAlert.alertDefault('warn', title, message, true);
|
|
break;
|
|
case 'Notice':
|
|
case 'User Notice':
|
|
case 'Runtime Notice':
|
|
if (title)
|
|
title += ': PHP ' + error.severity;
|
|
else
|
|
title = 'PHP ' + error.severity;
|
|
app.config.globalProperties.$fhcAlert.alertDefault('info', title, message, true);
|
|
break;
|
|
default:
|
|
message = 'Type: PHP ' + error.severity + '\n\n' + message;
|
|
if (title)
|
|
message = title + '\n\n' + message;
|
|
app.config.globalProperties.$fhcAlert.alertSystemError(message);
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
exception(errors) {
|
|
_send_array_or_object(errors, (error, title) => {
|
|
var message = '';
|
|
if (title)
|
|
message += title + '\n\n';
|
|
message += 'Type: ' + error.class + '\n\n';
|
|
message += 'Message: ' + error.message + '\n\n';
|
|
message += 'Filename: ' + error.filename + '\n';
|
|
message += 'Line Number: ' + error.line + '\n';
|
|
if (error.backtrace && error.backtrace.length) {
|
|
message += '\nBacktrace: ';
|
|
error.backtrace.forEach(err => {
|
|
message += '\n\tFile: ' + err.file + '\n';
|
|
message += '\tLine: ' + err.line + '\n';
|
|
message += '\tFunction: ' + err.function + '\n';
|
|
});
|
|
}
|
|
app.config.globalProperties.$fhcAlert.alertSystemError(message);
|
|
});
|
|
},
|
|
db(errors) {
|
|
_send_array_or_object(errors, (error, title) => {
|
|
var message = '';
|
|
if (title)
|
|
message += title + '\n\n';
|
|
if (error.heading !== undefined)
|
|
message += error.heading + '\n\n';
|
|
if (error.code !== undefined)
|
|
message += 'Code: ' + error.code + '\n\n';
|
|
if (error.sql !== undefined)
|
|
message += 'SQL: ' + error.sql + '\n\n';
|
|
if (error.message !== undefined)
|
|
message += 'Message: ' + error.message + '\n\n';
|
|
else if (error.messages !== undefined)
|
|
message += 'Messages: ' + error.messages.join('\n\t') + '\n\n';
|
|
if (error.filename !== undefined)
|
|
message += 'Filename: ' + error.filename + '\n';
|
|
if (error.line !== undefined)
|
|
message += 'Line Number: ' + error.line + '\n';
|
|
|
|
app.config.globalProperties.$fhcAlert.alertSystemError(message);
|
|
});
|
|
},
|
|
auth(errors) {
|
|
_send_array_or_object(errors, (error, title) => {
|
|
if (title)
|
|
title += ': ' + error.message;
|
|
else
|
|
title = error.message;
|
|
|
|
var message = '';
|
|
message += 'Controller name: ' + error.controller + '\n';
|
|
message += 'Method name: ' + error.method + '\n';
|
|
message += 'Required permissions: ' + error.required_permissions;
|
|
|
|
app.config.globalProperties.$fhcAlert.alertDefault(
|
|
'error',
|
|
title,
|
|
message,
|
|
true
|
|
);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
if (options?.errorHandling !== undefined)
|
|
DEFAULT_ERROR_CONFIG = _merge_error_config(options.errorHandling);
|
|
|
|
function get_config(form, uri, data, config) {
|
|
if (typeof form == 'string' && config === undefined) {
|
|
[uri, data, config] = [form, uri, data];
|
|
form = undefined;
|
|
} else if (form) {
|
|
if (typeof form != 'object')
|
|
throw new TypeError('Parameter 1 of _get_config must be an object or a string');
|
|
if (uri === undefined && data === undefined && config === undefined) {
|
|
config = form;
|
|
form = undefined;
|
|
}
|
|
}
|
|
if (form) {
|
|
// NOTE(chris): check if form is fhc-form
|
|
if (!form.clearValidation || !form.setFeedback)
|
|
throw new TypeError("'form' is not a Form Component");
|
|
|
|
form = {
|
|
clearValidation: form.clearValidation,
|
|
setFeedback: form.setFeedback
|
|
};
|
|
|
|
if (config)
|
|
config.form = form;
|
|
else
|
|
config = {form};
|
|
}
|
|
|
|
return [uri, data, config];
|
|
}
|
|
function clean_return_value(response) {
|
|
if (typeof response.data === 'string' || response.data instanceof String)
|
|
return clean_return_value({ data: response });
|
|
|
|
const result = response.data;
|
|
delete response.data;
|
|
if (!result)
|
|
return {meta: {response}, data: null};
|
|
if (!result.meta)
|
|
result.meta = { response };
|
|
else
|
|
result.meta.response = response;
|
|
return result;
|
|
}
|
|
function _merge_error_config(config) {
|
|
if (config === false || config === 'off')
|
|
return { ...DEFAULT_ERROR_CONFIG, success: false, fail: false };
|
|
|
|
if (!config || config === true)
|
|
return { ...DEFAULT_ERROR_CONFIG };
|
|
|
|
if (config === 'success')
|
|
return { ...DEFAULT_ERROR_CONFIG, fail: false };
|
|
|
|
if (config === 'fail')
|
|
return { ...DEFAULT_ERROR_CONFIG, success: false };
|
|
|
|
const { success, fail, handler, combine } = config;
|
|
|
|
config = { ...DEFAULT_ERROR_CONFIG };
|
|
|
|
Object.entries({ fail, success }).forEach(([key, value]) => {
|
|
if (value !== undefined)
|
|
config[key] = value;
|
|
});
|
|
Object.entries({ handler, combine }).forEach(([key, value]) => {
|
|
if (value !== undefined)
|
|
config[key] = { ...config[key], ...value };
|
|
});
|
|
|
|
return config;
|
|
}
|
|
function get_error_handler(config) {
|
|
const result = _merge_error_config(config?.errorHandling);
|
|
|
|
if (!config?.form) {
|
|
result.combine = { ...result.combine, form: [] };
|
|
} else {
|
|
const formHandler = result.handler.form;
|
|
result.handler = { ...result.handler, form: errors => formHandler(config.form, errors) };
|
|
}
|
|
|
|
return result;
|
|
}
|
|
function get_error_list(error) {
|
|
if (error.response) {
|
|
if (error.response.status == 404) {
|
|
return [{
|
|
type: 'not_found',
|
|
message: error.message,
|
|
url: error.request.responseURL
|
|
}];
|
|
} else {
|
|
if (error.response.data.errors == undefined) return [];
|
|
return error.response.data.errors;
|
|
}
|
|
} else if (error.request) {
|
|
return [{
|
|
type: 'site_failed',
|
|
message: error.message,
|
|
url: error.request.responseURL
|
|
}];
|
|
} else {
|
|
return [{
|
|
type: 'script',
|
|
message: error.message
|
|
}];
|
|
}
|
|
}
|
|
function popHandleableErrors(errorHandling, errors) {
|
|
const result = {};
|
|
const copy = [];
|
|
|
|
if (errors == undefined) return {};
|
|
|
|
while (errors.length)
|
|
copy.push(errors.pop());
|
|
for (var error of copy) {
|
|
let type = error.type;
|
|
let newType = null;
|
|
for (var t in errorHandling.combine) {
|
|
let newTypeCombinesType = errorHandling
|
|
.combine[t]
|
|
.includes(type);
|
|
let newTypeHasHandler = errorHandling.handler[t];
|
|
if (newTypeCombinesType && newTypeHasHandler) {
|
|
newType = t;
|
|
if (newType == 'form')
|
|
break;
|
|
}
|
|
}
|
|
if (newType)
|
|
type = newType;
|
|
const handler = errorHandling.handler[type];
|
|
if (handler) {
|
|
if (!result[type])
|
|
result[type] = [];
|
|
if (Array.isArray(error))
|
|
result[type].push(...error);
|
|
else
|
|
result[type].push(error);
|
|
continue;
|
|
}
|
|
errors.push(error);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
const fhcApiAxios = axios.create({
|
|
timeout: 500000,
|
|
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root
|
|
+ FHC_JS_DATA_STORAGE_OBJECT.ci_router
|
|
+ "/"
|
|
});
|
|
|
|
fhcApiAxios.interceptors.request.use(config => {
|
|
if (config.method != 'post' || !config.data)
|
|
return config;
|
|
|
|
if (config.data instanceof FormData)
|
|
return config;
|
|
|
|
if (!Object.values(config.data).every(item => {
|
|
if (item instanceof FileList)
|
|
return false;
|
|
if (Array.isArray(item))
|
|
return item.every(i => !(i instanceof File));
|
|
return true;
|
|
})) {
|
|
const newData = Object.entries(config.data).reduce((nd, [key, item]) => {
|
|
if (item instanceof FileList) {
|
|
for (const file of item)
|
|
nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file);
|
|
} else if (Array.isArray(item)) {
|
|
if (item.every(i => !(i instanceof File))) {
|
|
nd.jsondata[key] = item;
|
|
} else {
|
|
item.forEach(file => nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file));
|
|
}
|
|
} else {
|
|
nd.jsondata[key] = item;
|
|
}
|
|
return nd;
|
|
}, {
|
|
FormData: new FormData(),
|
|
jsondata: {}
|
|
});
|
|
newData.FormData.append('_jsondata', JSON.stringify(newData.jsondata));
|
|
config.data = newData.FormData;
|
|
}
|
|
|
|
return config;
|
|
});
|
|
|
|
fhcApiAxios.interceptors.response.use(
|
|
response => {
|
|
const errorConfig = get_error_handler(response.config);
|
|
|
|
if (!errorConfig.success)
|
|
return clean_return_value(response);
|
|
|
|
const errors = popHandleableErrors(errorConfig, response.data.errors);
|
|
|
|
for (var type in errors) {
|
|
errorConfig.handler[type](errors[type]);
|
|
}
|
|
|
|
return clean_return_value(response);
|
|
},
|
|
error => {
|
|
if (error.code == 'ERR_CANCELED')
|
|
return Promise.reject({ handled: true, ...error });
|
|
|
|
const errorConfig = get_error_handler(error.config);
|
|
|
|
if (!errorConfig.fail)
|
|
return Promise.reject(error);
|
|
|
|
const remaining = get_error_list(error);
|
|
|
|
const errors = popHandleableErrors(errorConfig, remaining);
|
|
|
|
for (var type in errors) {
|
|
errorConfig.handler[type](errors[type]);
|
|
}
|
|
|
|
if (remaining.length)
|
|
return Promise.reject(error);
|
|
|
|
return Promise.reject({ handled: true, ...error });
|
|
}
|
|
);
|
|
|
|
app.config.globalProperties.$api = {
|
|
getUri(url) {
|
|
return fhcApiAxios.getUri({url});
|
|
},
|
|
get(form, uri, params, config) {
|
|
[uri, params, config] = get_config(form, uri, params, config);
|
|
if (params) {
|
|
if (config)
|
|
config.params = params;
|
|
else
|
|
config = {params};
|
|
}
|
|
return fhcApiAxios.get(uri, config);
|
|
},
|
|
post(form, uri, data, config) {
|
|
[uri, data, config] = get_config(form, uri, data, config);
|
|
return fhcApiAxios.post(uri, data, config);
|
|
},
|
|
call(factory, configoverwrite, form) {
|
|
if (Array.isArray(factory)) {
|
|
const $api = app.config.globalProperties.$api;
|
|
|
|
return Promise
|
|
.allSettled(factory.map((config, index) => {
|
|
if (!Array.isArray(config))
|
|
config = ['#' + index, config];
|
|
return $api.call(config[1], {
|
|
errorHeader: config[0],
|
|
errorHandling: false
|
|
});
|
|
}))
|
|
.then(result => {
|
|
const [ , , config ] = get_config(form, undefined, undefined, configoverwrite || {});
|
|
const errorConfig = get_error_handler(config);
|
|
|
|
if (!errorConfig.success && !errorConfig.fail) {
|
|
return result;
|
|
}
|
|
|
|
const typedErrors = {};
|
|
for (var res of result) {
|
|
const [ allowed, item ] = res.status === 'fulfilled'
|
|
? [ errorConfig.success, res.value ]
|
|
: [ errorConfig.fail, res.reason ];
|
|
if (!allowed)
|
|
return;
|
|
|
|
const errors = popHandleableErrors(errorConfig, get_error_list(item));
|
|
|
|
for (var type in errors) {
|
|
if (!typedErrors[type])
|
|
typedErrors[type] = {
|
|
[item.config.errorHeader]: errors[type]
|
|
};
|
|
else
|
|
typedErrors[type][item.config.errorHeader] = errors[type];
|
|
}
|
|
};
|
|
|
|
for (var errType in typedErrors) {
|
|
errorConfig.handler[errType](typedErrors[errType]);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
}
|
|
let { method, url, params, config } = factory;
|
|
if (configoverwrite !== undefined) {
|
|
config = configoverwrite;
|
|
}
|
|
if (!method) {
|
|
method = 'get';
|
|
}
|
|
if (method.toLowerCase)
|
|
method = method.toLowerCase();
|
|
if (method == 'get') {
|
|
return this.get(form, url, params, config);
|
|
} else if (method == 'post') {
|
|
return this.post(form, url, params, config);
|
|
} else {
|
|
console.error("FhcApi: method not allowed:", method);
|
|
}
|
|
},
|
|
getErrorHandler(config) {
|
|
return get_error_handler(config);
|
|
}
|
|
};
|
|
|
|
app.provide('$api', app.config.globalProperties.$api);
|
|
}
|
|
};
|