Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

620 рядки
16 KiB

  1. // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. // MIT License. See license.txt
  3. // My HTTP Request
  4. frappe.provide('frappe.request');
  5. frappe.provide('frappe.request.error_handlers');
  6. frappe.request.url = '/';
  7. frappe.request.ajax_count = 0;
  8. frappe.request.waiting_for_ajax = [];
  9. frappe.request.logs = {};
  10. frappe.xcall = function(method, params) {
  11. return new Promise((resolve, reject) => {
  12. frappe.call({
  13. method: method,
  14. args: params,
  15. callback: (r) => {
  16. resolve(r.message);
  17. },
  18. error: (r) => {
  19. reject(r.message);
  20. }
  21. });
  22. });
  23. };
  24. // generic server call (call page, object)
  25. frappe.call = function(opts) {
  26. if (!frappe.is_online()) {
  27. frappe.show_alert({
  28. indicator: 'orange',
  29. message: __('Connection Lost'),
  30. subtitle: __('You are not connected to Internet. Retry after sometime.')
  31. }, 3);
  32. opts.always && opts.always();
  33. return $.ajax();
  34. }
  35. if (typeof arguments[0]==='string') {
  36. opts = {
  37. method: arguments[0],
  38. args: arguments[1],
  39. callback: arguments[2],
  40. headers: arguments[3]
  41. }
  42. }
  43. if(opts.quiet) {
  44. opts.no_spinner = true;
  45. }
  46. var args = $.extend({}, opts.args);
  47. // cmd
  48. if(opts.module && opts.page) {
  49. args.cmd = opts.module+'.page.'+opts.page+'.'+opts.page+'.'+opts.method;
  50. } else if(opts.doc) {
  51. $.extend(args, {
  52. cmd: "run_doc_method",
  53. docs: frappe.get_doc(opts.doc.doctype, opts.doc.name),
  54. method: opts.method,
  55. args: opts.args,
  56. });
  57. } else if(opts.method) {
  58. args.cmd = opts.method;
  59. }
  60. var callback = function(data, response_text) {
  61. if(data.task_id) {
  62. // async call, subscribe
  63. frappe.socketio.subscribe(data.task_id, opts);
  64. if(opts.queued) {
  65. opts.queued(data);
  66. }
  67. }
  68. else if (opts.callback) {
  69. // ajax
  70. return opts.callback(data, response_text);
  71. }
  72. }
  73. let url = opts.url;
  74. if (!url) {
  75. url = '/api/method/' + args.cmd;
  76. if (window.cordova) {
  77. let host = frappe.request.url;
  78. host = host.slice(0, host.length - 1);
  79. url = host + url;
  80. }
  81. delete args.cmd;
  82. }
  83. // debouce if required
  84. if (opts.debounce && frappe.request.is_fresh(args, opts.debounce)) {
  85. return Promise.resolve();
  86. }
  87. return frappe.request.call({
  88. type: opts.type || "POST",
  89. args: args,
  90. success: callback,
  91. error: opts.error,
  92. always: opts.always,
  93. btn: opts.btn,
  94. freeze: opts.freeze,
  95. freeze_message: opts.freeze_message,
  96. headers: opts.headers || {},
  97. error_handlers: opts.error_handlers || {},
  98. // show_spinner: !opts.no_spinner,
  99. async: opts.async,
  100. silent: opts.silent,
  101. url,
  102. });
  103. }
  104. frappe.request.call = function(opts) {
  105. frappe.request.prepare(opts);
  106. var statusCode = {
  107. 200: function(data, xhr) {
  108. opts.success_callback && opts.success_callback(data, xhr.responseText);
  109. },
  110. 401: function(xhr) {
  111. if(frappe.app.session_expired_dialog && frappe.app.session_expired_dialog.display) {
  112. frappe.app.redirect_to_login();
  113. } else {
  114. frappe.app.handle_session_expired();
  115. }
  116. },
  117. 404: function(xhr) {
  118. if (frappe.flags.setting_original_route) {
  119. // original route is wrong, redirect to login
  120. frappe.app.redirect_to_login();
  121. } else {
  122. frappe.msgprint({title: __("Not found"), indicator: 'red',
  123. message: __('The resource you are looking for is not available')});
  124. }
  125. },
  126. 403: function(xhr) {
  127. if (frappe.session.user === "Guest" && frappe.session.logged_in_user !== "Guest") {
  128. // session expired
  129. frappe.app.handle_session_expired();
  130. } else if (xhr.responseJSON && xhr.responseJSON._error_message) {
  131. frappe.msgprint({
  132. title: __("Not permitted"), indicator: 'red',
  133. message: xhr.responseJSON._error_message
  134. });
  135. xhr.responseJSON._server_messages = null;
  136. } else if (xhr.responseJSON && xhr.responseJSON._server_messages) {
  137. var _server_messages = JSON.parse(xhr.responseJSON._server_messages);
  138. // avoid double messages
  139. if (_server_messages.indexOf(__("Not permitted")) !== -1) {
  140. return;
  141. }
  142. } else {
  143. frappe.msgprint({
  144. title: __("Not permitted"), indicator: 'red',
  145. message: __('You do not have enough permissions to access this resource. Please contact your manager to get access.')});
  146. }
  147. },
  148. 508: function(xhr) {
  149. frappe.utils.play_sound("error");
  150. frappe.msgprint({title:__('Please try again'), indicator:'red',
  151. message:__("Another transaction is blocking this one. Please try again in a few seconds.")});
  152. },
  153. 413: function(data, xhr) {
  154. frappe.msgprint({indicator:'red', title:__('File too big'), message:__("File size exceeded the maximum allowed size of {0} MB",
  155. [(frappe.boot.max_file_size || 5242880) / 1048576])});
  156. },
  157. 417: function(xhr) {
  158. var r = xhr.responseJSON;
  159. if (!r) {
  160. try {
  161. r = JSON.parse(xhr.responseText);
  162. } catch (e) {
  163. r = xhr.responseText;
  164. }
  165. }
  166. opts.error_callback && opts.error_callback(r);
  167. },
  168. 501: function(data, xhr) {
  169. if(typeof data === "string") data = JSON.parse(data);
  170. opts.error_callback && opts.error_callback(data, xhr.responseText);
  171. },
  172. 500: function(xhr) {
  173. frappe.utils.play_sound("error");
  174. try {
  175. opts.error_callback && opts.error_callback();
  176. frappe.request.report_error(xhr, opts);
  177. } catch (e) {
  178. frappe.request.report_error(xhr, opts);
  179. }
  180. },
  181. 504: function(xhr) {
  182. frappe.msgprint(__("Request Timed Out"))
  183. opts.error_callback && opts.error_callback();
  184. },
  185. 502: function(xhr) {
  186. frappe.msgprint(__("Internal Server Error"));
  187. }
  188. };
  189. var exception_handlers = {
  190. 'QueryTimeoutError': function() {
  191. frappe.utils.play_sound("error");
  192. frappe.msgprint({
  193. title: __('Request Timeout'),
  194. indicator: 'red',
  195. message: __("Server was too busy to process this request. Please try again.")
  196. });
  197. },
  198. 'QueryDeadlockError': function() {
  199. frappe.utils.play_sound("error");
  200. frappe.msgprint({
  201. title: __('Deadlock Occurred'),
  202. indicator: 'red',
  203. message: __("Server was too busy to process this request. Please try again.")
  204. });
  205. }
  206. };
  207. var ajax_args = {
  208. url: opts.url || frappe.request.url,
  209. data: opts.args,
  210. type: opts.type,
  211. dataType: opts.dataType || 'json',
  212. async: opts.async,
  213. headers: Object.assign({
  214. "X-Frappe-CSRF-Token": frappe.csrf_token,
  215. "Accept": "application/json",
  216. "X-Frappe-CMD": (opts.args && opts.args.cmd || '') || ''
  217. }, opts.headers),
  218. cache: false
  219. };
  220. if (opts.args && opts.args.doctype) {
  221. ajax_args.headers["X-Frappe-Doctype"] = encodeURIComponent(opts.args.doctype);
  222. }
  223. frappe.last_request = ajax_args.data;
  224. return $.ajax(ajax_args)
  225. .done(function(data, textStatus, xhr) {
  226. try {
  227. if(typeof data === "string") data = JSON.parse(data);
  228. // sync attached docs
  229. if(data.docs || data.docinfo) {
  230. frappe.model.sync(data);
  231. }
  232. // sync translated messages
  233. if(data.__messages) {
  234. $.extend(frappe._messages, data.__messages);
  235. }
  236. // sync link titles
  237. if (data._link_titles) {
  238. if (!frappe._link_titles) {
  239. frappe._link_titles = {};
  240. }
  241. $.extend(frappe._link_titles, data._link_titles);
  242. }
  243. // callbacks
  244. var status_code_handler = statusCode[xhr.statusCode().status];
  245. if (status_code_handler) {
  246. status_code_handler(data, xhr);
  247. }
  248. } catch(e) {
  249. console.log("Unable to handle success response", data); // eslint-disable-line
  250. console.trace(e); // eslint-disable-line
  251. }
  252. })
  253. .always(function(data, textStatus, xhr) {
  254. try {
  255. if(typeof data==="string") {
  256. data = JSON.parse(data);
  257. }
  258. if(data.responseText) {
  259. var xhr = data;
  260. data = JSON.parse(data.responseText);
  261. }
  262. } catch(e) {
  263. data = null;
  264. // pass
  265. }
  266. frappe.request.cleanup(opts, data);
  267. if(opts.always) {
  268. opts.always(data);
  269. }
  270. })
  271. .fail(function(xhr, textStatus) {
  272. try {
  273. if (xhr.getResponseHeader('content-type') == 'application/json' && xhr.responseText) {
  274. var data;
  275. try {
  276. data = JSON.parse(xhr.responseText);
  277. } catch (e) {
  278. console.log("Unable to parse reponse text");
  279. console.log(xhr.responseText);
  280. console.log(e);
  281. }
  282. if (data && data.exception) {
  283. // frappe.exceptions.CustomError: (1024, ...) -> CustomError
  284. var exception = data.exception.split('.').at(-1).split(':').at(0);
  285. var exception_handler = exception_handlers[exception];
  286. if (exception_handler) {
  287. exception_handler(data);
  288. return;
  289. }
  290. }
  291. }
  292. var status_code_handler = statusCode[xhr.statusCode().status];
  293. if (status_code_handler) {
  294. status_code_handler(xhr);
  295. return;
  296. }
  297. // if not handled by error handler!
  298. opts.error_callback && opts.error_callback(xhr);
  299. } catch(e) {
  300. console.log("Unable to handle failed response"); // eslint-disable-line
  301. console.trace(e); // eslint-disable-line
  302. }
  303. });
  304. }
  305. frappe.request.is_fresh = function(args, threshold) {
  306. // return true if a request with similar args has been sent recently
  307. if (!frappe.request.logs[args.cmd]) {
  308. frappe.request.logs[args.cmd] = [];
  309. }
  310. for (let past_request of frappe.request.logs[args.cmd]) {
  311. // check if request has same args and was made recently
  312. if ((new Date() - past_request.timestamp) < threshold
  313. && frappe.utils.deep_equal(args, past_request.args)) {
  314. // eslint-disable-next-line no-console
  315. console.log('throttled');
  316. return true;
  317. }
  318. }
  319. // log the request
  320. frappe.request.logs[args.cmd].push({args: args, timestamp: new Date()});
  321. return false;
  322. };
  323. // call execute serverside request
  324. frappe.request.prepare = function(opts) {
  325. $("body").attr("data-ajax-state", "triggered");
  326. // btn indicator
  327. if(opts.btn) $(opts.btn).prop("disabled", true);
  328. // freeze page
  329. if(opts.freeze) frappe.dom.freeze(opts.freeze_message);
  330. // stringify args if required
  331. for(var key in opts.args) {
  332. if(opts.args[key] && ($.isPlainObject(opts.args[key]) || $.isArray(opts.args[key]))) {
  333. opts.args[key] = JSON.stringify(opts.args[key]);
  334. }
  335. }
  336. // no cmd?
  337. if(!opts.args.cmd && !opts.url) {
  338. console.log(opts)
  339. throw "Incomplete Request";
  340. }
  341. opts.success_callback = opts.success;
  342. opts.error_callback = opts.error;
  343. delete opts.success;
  344. delete opts.error;
  345. }
  346. frappe.request.cleanup = function(opts, r) {
  347. // stop button indicator
  348. if(opts.btn) {
  349. $(opts.btn).prop("disabled", false);
  350. }
  351. $("body").attr("data-ajax-state", "complete");
  352. // un-freeze page
  353. if(opts.freeze) frappe.dom.unfreeze();
  354. if(r) {
  355. // session expired? - Guest has no business here!
  356. if (r.session_expired ||
  357. (frappe.session.user === 'Guest' && frappe.session.logged_in_user !== "Guest")) {
  358. frappe.app.handle_session_expired();
  359. return;
  360. }
  361. // error handlers
  362. let global_handlers = frappe.request.error_handlers[r.exc_type] || [];
  363. let request_handler = opts.error_handlers ? opts.error_handlers[r.exc_type] : null;
  364. let handlers = [].concat(global_handlers, request_handler).filter(Boolean);
  365. if (r.exc_type) {
  366. handlers.forEach(handler => {
  367. handler(r);
  368. });
  369. }
  370. // show messages
  371. if(r._server_messages && !opts.silent) {
  372. // show server messages if no handlers exist
  373. if (handlers.length === 0) {
  374. r._server_messages = JSON.parse(r._server_messages);
  375. frappe.hide_msgprint();
  376. frappe.msgprint(r._server_messages);
  377. }
  378. }
  379. // show errors
  380. if(r.exc) {
  381. r.exc = JSON.parse(r.exc);
  382. if(r.exc instanceof Array) {
  383. $.each(r.exc, function(i, v) {
  384. if(v) {
  385. console.log(v);
  386. }
  387. })
  388. } else {
  389. console.log(r.exc);
  390. }
  391. }
  392. // debug messages
  393. if(r._debug_messages) {
  394. if(opts.args) {
  395. console.log("======== arguments ========");
  396. console.log(opts.args);
  397. console.log("========")
  398. }
  399. $.each(JSON.parse(r._debug_messages), function(i, v) { console.log(v); });
  400. console.log("======== response ========");
  401. delete r._debug_messages;
  402. console.log(r);
  403. console.log("========");
  404. }
  405. }
  406. frappe.last_response = r;
  407. }
  408. frappe.after_server_call = () => {
  409. if(frappe.request.ajax_count) {
  410. return new Promise(resolve => {
  411. frappe.request.waiting_for_ajax.push(() => {
  412. resolve();
  413. });
  414. });
  415. } else {
  416. return null;
  417. }
  418. };
  419. frappe.after_ajax = function(fn) {
  420. return new Promise(resolve => {
  421. if(frappe.request.ajax_count) {
  422. frappe.request.waiting_for_ajax.push(() => {
  423. if(fn) return resolve(fn());
  424. resolve();
  425. });
  426. } else {
  427. if(fn) return resolve(fn());
  428. resolve();
  429. }
  430. });
  431. };
  432. frappe.request.report_error = function(xhr, request_opts) {
  433. var data = JSON.parse(xhr.responseText);
  434. var exc;
  435. if (data.exc) {
  436. try {
  437. exc = (JSON.parse(data.exc) || []).join("\n");
  438. } catch (e) {
  439. exc = data.exc;
  440. }
  441. delete data.exc;
  442. } else {
  443. exc = "";
  444. }
  445. const copy_markdown_to_clipboard = () => {
  446. const code_block = snippet => '```\n' + snippet + '\n```';
  447. const traceback_info = [
  448. '### App Versions',
  449. code_block(JSON.stringify(frappe.boot.versions, null, "\t")),
  450. '### Route',
  451. code_block(frappe.get_route_str()),
  452. '### Trackeback',
  453. code_block(exc),
  454. '### Request Data',
  455. code_block(JSON.stringify(request_opts, null, "\t")),
  456. '### Response Data',
  457. code_block(JSON.stringify(data, null, '\t')),
  458. ].join("\n");
  459. frappe.utils.copy_to_clipboard(traceback_info);
  460. };
  461. var show_communication = function() {
  462. var error_report_message = [
  463. '<h5>Please type some additional information that could help us reproduce this issue:</h5>',
  464. '<div style="min-height: 100px; border: 1px solid #bbb; \
  465. border-radius: 5px; padding: 15px; margin-bottom: 15px;"></div>',
  466. '<hr>',
  467. '<h5>App Versions</h5>',
  468. '<pre>' + JSON.stringify(frappe.boot.versions, null, "\t") + '</pre>',
  469. '<h5>Route</h5>',
  470. '<pre>' + frappe.get_route_str() + '</pre>',
  471. '<hr>',
  472. '<h5>Error Report</h5>',
  473. '<pre>' + exc + '</pre>',
  474. '<hr>',
  475. '<h5>Request Data</h5>',
  476. '<pre>' + JSON.stringify(request_opts, null, "\t") + '</pre>',
  477. '<hr>',
  478. '<h5>Response JSON</h5>',
  479. '<pre>' + JSON.stringify(data, null, '\t')+ '</pre>'
  480. ].join("\n");
  481. var communication_composer = new frappe.views.CommunicationComposer({
  482. subject: 'Error Report [' + frappe.datetime.nowdate() + ']',
  483. recipients: error_report_email,
  484. message: error_report_message,
  485. doc: {
  486. doctype: "User",
  487. name: frappe.session.user
  488. }
  489. });
  490. communication_composer.dialog.$wrapper.css("z-index", cint(frappe.msg_dialog.$wrapper.css("z-index")) + 1);
  491. }
  492. if (exc) {
  493. var error_report_email = frappe.boot.error_report_email;
  494. request_opts = frappe.request.cleanup_request_opts(request_opts);
  495. // window.msg_dialog = frappe.msgprint({message:error_message, indicator:'red', big: true});
  496. if (!frappe.error_dialog) {
  497. frappe.error_dialog = new frappe.ui.Dialog({
  498. title: __('Server Error'),
  499. primary_action_label: __('Report'),
  500. primary_action: () => {
  501. if (error_report_email) {
  502. show_communication();
  503. } else {
  504. frappe.msgprint(__('Support Email Address Not Specified'));
  505. }
  506. frappe.error_dialog.hide();
  507. },
  508. secondary_action_label: __('Copy error to clipboard'),
  509. secondary_action: () => {
  510. copy_markdown_to_clipboard();
  511. frappe.error_dialog.hide();
  512. }
  513. });
  514. frappe.error_dialog.wrapper.classList.add('msgprint-dialog');
  515. }
  516. let parts = strip(exc).split('\n');
  517. frappe.error_dialog.$body.html(parts[parts.length - 1]);
  518. frappe.error_dialog.show();
  519. }
  520. };
  521. frappe.request.cleanup_request_opts = function(request_opts) {
  522. var doc = (request_opts.args || {}).doc;
  523. if (doc) {
  524. doc = JSON.parse(doc);
  525. $.each(Object.keys(doc), function(i, key) {
  526. if (key.indexOf("password")!==-1 && doc[key]) {
  527. // mask the password
  528. doc[key] = "*****";
  529. }
  530. });
  531. request_opts.args.doc = JSON.stringify(doc);
  532. }
  533. return request_opts;
  534. };
  535. frappe.request.on_error = function(error_type, handler) {
  536. frappe.request.error_handlers[error_type] = frappe.request.error_handlers[error_type] || [];
  537. frappe.request.error_handlers[error_type].push(handler);
  538. }
  539. $(document).ajaxSend(function() {
  540. frappe.request.ajax_count++;
  541. });
  542. $(document).ajaxComplete(function() {
  543. frappe.request.ajax_count--;
  544. if(!frappe.request.ajax_count) {
  545. $.each(frappe.request.waiting_for_ajax || [], function(i, fn) {
  546. fn();
  547. });
  548. frappe.request.waiting_for_ajax = [];
  549. }
  550. });