seitime-frappe/frappe/public/js/frappe/request.js
2023-07-14 15:25:48 +05:30

645 lines
16 KiB
JavaScript

// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
// My HTTP Request
frappe.provide("frappe.request");
frappe.provide("frappe.request.error_handlers");
frappe.request.url = "/";
frappe.request.ajax_count = 0;
frappe.request.waiting_for_ajax = [];
frappe.request.logs = {};
frappe.xcall = function (method, params) {
return new Promise((resolve, reject) => {
frappe.call({
method: method,
args: params,
callback: (r) => {
resolve(r.message);
},
error: (r) => {
reject(r.message);
},
});
});
};
// generic server call (call page, object)
frappe.call = function (opts) {
if (!frappe.is_online()) {
frappe.show_alert(
{
indicator: "orange",
message: __("Connection Lost"),
subtitle: __("You are not connected to Internet. Retry after sometime."),
},
3
);
opts.always && opts.always();
return $.ajax();
}
if (typeof arguments[0] === "string") {
opts = {
method: arguments[0],
args: arguments[1],
callback: arguments[2],
headers: arguments[3],
};
}
if (opts.quiet) {
opts.no_spinner = true;
}
var args = $.extend({}, opts.args);
if (args.freeze) {
opts.freeze = opts.freeze || args.freeze;
opts.freeze_message = opts.freeze_message || args.freeze_message;
}
// cmd
if (opts.module && opts.page) {
args.cmd = opts.module + ".page." + opts.page + "." + opts.page + "." + opts.method;
} else if (opts.doc) {
$.extend(args, {
cmd: "run_doc_method",
docs: frappe.get_doc(opts.doc.doctype, opts.doc.name),
method: opts.method,
args: opts.args,
});
} else if (opts.method) {
args.cmd = opts.method;
}
var callback = function (data, response_text) {
if (data.task_id) {
// async call, subscribe
frappe.realtime.subscribe(data.task_id, opts);
if (opts.queued) {
opts.queued(data);
}
} else if (opts.callback) {
// ajax
return opts.callback(data, response_text);
}
};
let url = opts.url;
if (!url) {
url = "/api/method/" + args.cmd;
if (window.cordova) {
let host = frappe.request.url;
host = host.slice(0, host.length - 1);
url = host + url;
}
delete args.cmd;
}
// debouce if required
if (opts.debounce && frappe.request.is_fresh(args, opts.debounce)) {
return Promise.resolve();
}
return frappe.request.call({
type: opts.type || "POST",
args: args,
success: callback,
error: opts.error,
always: opts.always,
btn: opts.btn,
freeze: opts.freeze,
freeze_message: opts.freeze_message,
headers: opts.headers || {},
error_handlers: opts.error_handlers || {},
// show_spinner: !opts.no_spinner,
async: opts.async,
silent: opts.silent,
url,
});
};
frappe.request.call = function (opts) {
frappe.request.prepare(opts);
var statusCode = {
200: function (data, xhr) {
opts.success_callback && opts.success_callback(data, xhr.responseText);
},
401: function (xhr) {
if (frappe.app.session_expired_dialog && frappe.app.session_expired_dialog.display) {
frappe.app.redirect_to_login();
} else {
frappe.app.handle_session_expired();
}
},
404: function (xhr) {
frappe.msgprint({
title: __("Not found"),
indicator: "red",
message: __("The resource you are looking for is not available"),
});
},
403: function (xhr) {
if (frappe.session.user === "Guest" && frappe.session.logged_in_user !== "Guest") {
// session expired
frappe.app.handle_session_expired();
} else if (xhr.responseJSON && xhr.responseJSON._error_message) {
frappe.msgprint({
title: __("Not permitted"),
indicator: "red",
message: xhr.responseJSON._error_message,
});
xhr.responseJSON._server_messages = null;
} else if (xhr.responseJSON && xhr.responseJSON._server_messages) {
var _server_messages = JSON.parse(xhr.responseJSON._server_messages);
// avoid double messages
if (_server_messages.indexOf(__("Not permitted")) !== -1) {
return;
}
} else {
frappe.msgprint({
title: __("Not permitted"),
indicator: "red",
message: __(
"You do not have enough permissions to access this resource. Please contact your manager to get access."
),
});
}
},
508: function (xhr) {
frappe.utils.play_sound("error");
frappe.msgprint({
title: __("Please try again"),
indicator: "red",
message: __(
"Another transaction is blocking this one. Please try again in a few seconds."
),
});
},
413: function (data, xhr) {
frappe.msgprint({
indicator: "red",
title: __("File too big"),
message: __("File size exceeded the maximum allowed size of {0} MB", [
(frappe.boot.max_file_size || 5242880) / 1048576,
]),
});
},
417: function (xhr) {
var r = xhr.responseJSON;
if (!r) {
try {
r = JSON.parse(xhr.responseText);
} catch (e) {
r = xhr.responseText;
}
}
opts.error_callback && opts.error_callback(r);
},
501: function (data, xhr) {
if (typeof data === "string") data = JSON.parse(data);
opts.error_callback && opts.error_callback(data, xhr.responseText);
},
500: function (xhr) {
frappe.utils.play_sound("error");
try {
opts.error_callback && opts.error_callback();
frappe.request.report_error(xhr, opts);
} catch (e) {
frappe.request.report_error(xhr, opts);
}
},
504: function (xhr) {
frappe.msgprint(__("Request Timed Out"));
opts.error_callback && opts.error_callback();
},
502: function (xhr) {
frappe.msgprint(__("Internal Server Error"));
},
};
var exception_handlers = {
QueryTimeoutError: function () {
frappe.utils.play_sound("error");
frappe.msgprint({
title: __("Request Timeout"),
indicator: "red",
message: __("Server was too busy to process this request. Please try again."),
});
},
QueryDeadlockError: function () {
frappe.utils.play_sound("error");
frappe.msgprint({
title: __("Deadlock Occurred"),
indicator: "red",
message: __("Server was too busy to process this request. Please try again."),
});
},
};
var ajax_args = {
url: opts.url || frappe.request.url,
data: opts.args,
type: opts.type,
dataType: opts.dataType || "json",
async: opts.async,
headers: Object.assign(
{
"X-Frappe-CSRF-Token": frappe.csrf_token,
Accept: "application/json",
"X-Frappe-CMD": (opts.args && opts.args.cmd) || "" || "",
},
opts.headers
),
cache: false,
};
if (opts.args && opts.args.doctype) {
ajax_args.headers["X-Frappe-Doctype"] = encodeURIComponent(opts.args.doctype);
}
frappe.last_request = ajax_args.data;
return $.ajax(ajax_args)
.done(function (data, textStatus, xhr) {
try {
if (typeof data === "string") data = JSON.parse(data);
// sync attached docs
if (data.docs || data.docinfo) {
frappe.model.sync(data);
}
// sync translated messages
if (data.__messages) {
$.extend(frappe._messages, data.__messages);
}
// sync link titles
if (data._link_titles) {
if (!frappe._link_titles) {
frappe._link_titles = {};
}
$.extend(frappe._link_titles, data._link_titles);
}
// callbacks
var status_code_handler = statusCode[xhr.statusCode().status];
if (status_code_handler) {
status_code_handler(data, xhr);
}
} catch (e) {
console.log("Unable to handle success response", data); // eslint-disable-line
console.error(e); // eslint-disable-line
}
})
.always(function (data, textStatus, xhr) {
try {
if (typeof data === "string") {
data = JSON.parse(data);
}
if (data.responseText) {
var xhr = data; // eslint-disable-line
data = JSON.parse(data.responseText);
}
} catch (e) {
data = null;
// pass
}
frappe.request.cleanup(opts, data);
if (opts.always) {
opts.always(data);
}
})
.fail(function (xhr, textStatus) {
try {
if (
xhr.getResponseHeader("content-type") == "application/json" &&
xhr.responseText
) {
var data;
try {
data = JSON.parse(xhr.responseText);
} catch (e) {
console.log("Unable to parse reponse text");
console.log(xhr.responseText);
console.log(e);
}
if (data && data.exception) {
// frappe.exceptions.CustomError: (1024, ...) -> CustomError
var exception = data.exception.split(".").at(-1).split(":").at(0);
var exception_handler = exception_handlers[exception];
if (exception_handler) {
exception_handler(data);
return;
}
}
}
var status_code_handler = statusCode[xhr.statusCode().status];
if (status_code_handler) {
status_code_handler(xhr);
return;
}
// if not handled by error handler!
opts.error_callback && opts.error_callback(xhr);
} catch (e) {
console.log("Unable to handle failed response"); // eslint-disable-line
console.error(e); // eslint-disable-line
}
});
};
frappe.request.is_fresh = function (args, threshold) {
// return true if a request with similar args has been sent recently
if (!frappe.request.logs[args.cmd]) {
frappe.request.logs[args.cmd] = [];
}
for (let past_request of frappe.request.logs[args.cmd]) {
// check if request has same args and was made recently
if (
new Date() - past_request.timestamp < threshold &&
frappe.utils.deep_equal(args, past_request.args)
) {
// eslint-disable-next-line no-console
console.log("throttled");
return true;
}
}
// log the request
frappe.request.logs[args.cmd].push({ args: args, timestamp: new Date() });
return false;
};
// call execute serverside request
frappe.request.prepare = function (opts) {
$("body").attr("data-ajax-state", "triggered");
// btn indicator
if (opts.btn) $(opts.btn).prop("disabled", true);
// freeze page
if (opts.freeze) frappe.dom.freeze(opts.freeze_message);
// stringify args if required
for (var key in opts.args) {
if (opts.args[key] && ($.isPlainObject(opts.args[key]) || $.isArray(opts.args[key]))) {
opts.args[key] = JSON.stringify(opts.args[key]);
}
}
// no cmd?
if (!opts.args.cmd && !opts.url) {
console.log(opts);
throw "Incomplete Request";
}
opts.success_callback = opts.success;
opts.error_callback = opts.error;
delete opts.success;
delete opts.error;
};
frappe.request.cleanup = function (opts, r) {
// stop button indicator
if (opts.btn) {
$(opts.btn).prop("disabled", false);
}
$("body").attr("data-ajax-state", "complete");
// un-freeze page
if (opts.freeze) frappe.dom.unfreeze();
if (r) {
// session expired? - Guest has no business here!
if (
r.session_expired ||
(frappe.session.user === "Guest" && frappe.session.logged_in_user !== "Guest")
) {
frappe.app.handle_session_expired();
return;
}
// error handlers
let global_handlers = frappe.request.error_handlers[r.exc_type] || [];
let request_handler = opts.error_handlers ? opts.error_handlers[r.exc_type] : null;
let handlers = [].concat(global_handlers, request_handler).filter(Boolean);
if (r.exc_type) {
handlers.forEach((handler) => {
handler(r);
});
}
// show messages
if (r._server_messages && !opts.silent) {
// show server messages if no handlers exist
if (handlers.length === 0) {
r._server_messages = JSON.parse(r._server_messages);
frappe.hide_msgprint();
frappe.msgprint(r._server_messages);
}
}
// show errors
if (r.exc) {
r.exc = JSON.parse(r.exc);
if (r.exc instanceof Array) {
r.exc.forEach((exc) => {
if (exc) {
console.error(exc);
}
});
} else {
console.error(r.exc);
}
}
// debug messages
if (r._debug_messages) {
if (opts.args) {
console.log("======== arguments ========");
console.log(opts.args);
console.log("========");
}
$.each(JSON.parse(r._debug_messages), function (i, v) {
console.log(v);
});
console.log("======== response ========");
delete r._debug_messages;
console.log(r);
console.log("========");
}
}
frappe.last_response = r;
};
frappe.after_server_call = () => {
if (frappe.request.ajax_count) {
return new Promise((resolve) => {
frappe.request.waiting_for_ajax.push(() => {
resolve();
});
});
} else {
return null;
}
};
frappe.after_ajax = function (fn) {
return new Promise((resolve) => {
if (frappe.request.ajax_count) {
frappe.request.waiting_for_ajax.push(() => {
if (fn) return resolve(fn());
resolve();
});
} else {
if (fn) return resolve(fn());
resolve();
}
});
};
frappe.request.report_error = function (xhr, request_opts) {
var data = JSON.parse(xhr.responseText);
var exc;
if (data.exc) {
try {
exc = (JSON.parse(data.exc) || []).join("\n");
} catch (e) {
exc = data.exc;
}
delete data.exc;
} else {
exc = "";
}
const copy_markdown_to_clipboard = () => {
const code_block = (snippet) => "```\n" + snippet + "\n```";
const traceback_info = [
"### App Versions",
code_block(JSON.stringify(frappe.boot.versions, null, "\t")),
"### Route",
code_block(frappe.get_route_str()),
"### Traceback",
code_block(exc),
"### Request Data",
code_block(JSON.stringify(request_opts, null, "\t")),
"### Response Data",
code_block(JSON.stringify(data, null, "\t")),
].join("\n");
frappe.utils.copy_to_clipboard(traceback_info);
};
var show_communication = function () {
var error_report_message = [
"<h5>Please type some additional information that could help us reproduce this issue:</h5>",
'<div style="min-height: 100px; border: 1px solid #bbb; \
border-radius: 5px; padding: 15px; margin-bottom: 15px;"></div>',
"<hr>",
"<h5>App Versions</h5>",
"<pre>" + JSON.stringify(frappe.boot.versions, null, "\t") + "</pre>",
"<h5>Route</h5>",
"<pre>" + frappe.get_route_str() + "</pre>",
"<hr>",
"<h5>Error Report</h5>",
"<pre>" + exc + "</pre>",
"<hr>",
"<h5>Request Data</h5>",
"<pre>" + JSON.stringify(request_opts, null, "\t") + "</pre>",
"<hr>",
"<h5>Response JSON</h5>",
"<pre>" + JSON.stringify(data, null, "\t") + "</pre>",
].join("\n");
var communication_composer = new frappe.views.CommunicationComposer({
subject: "Error Report [" + frappe.datetime.nowdate() + "]",
recipients: error_report_email,
message: error_report_message,
doc: {
doctype: "User",
name: frappe.session.user,
},
});
communication_composer.dialog.$wrapper.css(
"z-index",
cint(frappe.msg_dialog.$wrapper.css("z-index")) + 1
);
};
if (exc) {
var error_report_email = frappe.boot.error_report_email;
request_opts = frappe.request.cleanup_request_opts(request_opts);
// window.msg_dialog = frappe.msgprint({message:error_message, indicator:'red', big: true});
if (!frappe.error_dialog) {
frappe.error_dialog = new frappe.ui.Dialog({
title: __("Server Error"),
primary_action_label: __("Report"),
primary_action: () => {
if (error_report_email) {
show_communication();
} else {
frappe.msgprint(__("Support Email Address Not Specified"));
}
frappe.error_dialog.hide();
},
secondary_action_label: __("Copy error to clipboard"),
secondary_action: () => {
copy_markdown_to_clipboard();
frappe.error_dialog.hide();
},
});
frappe.error_dialog.wrapper.classList.add("msgprint-dialog");
}
let parts = strip(exc).split("\n");
frappe.error_dialog.$body.html(parts[parts.length - 1]);
frappe.error_dialog.show();
}
};
frappe.request.cleanup_request_opts = function (request_opts) {
var doc = (request_opts.args || {}).doc;
if (doc) {
doc = JSON.parse(doc);
$.each(Object.keys(doc), function (i, key) {
if (key.indexOf("password") !== -1 && doc[key]) {
// mask the password
doc[key] = "*****";
}
});
request_opts.args.doc = JSON.stringify(doc);
}
return request_opts;
};
frappe.request.on_error = function (error_type, handler) {
frappe.request.error_handlers[error_type] = frappe.request.error_handlers[error_type] || [];
frappe.request.error_handlers[error_type].push(handler);
};
$(document).ajaxSend(function () {
frappe.request.ajax_count++;
});
$(document).ajaxComplete(function () {
frappe.request.ajax_count--;
if (!frappe.request.ajax_count) {
$.each(frappe.request.waiting_for_ajax || [], function (i, fn) {
fn();
});
frappe.request.waiting_for_ajax = [];
}
});