/* This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. */ (function() { 'use strict'; Sao.rpc = function(args, session=null, async=true, process_exception=true) { var dfd = jQuery.Deferred(), result; if (!session) { session = new Sao.Session(); } var params = jQuery.extend([], args.params); params.push(jQuery.extend({}, session.context, params.pop())); if (session.cache && session.cache.cached(args.method)) { result = session.cache.get( args.method, JSON.stringify(Sao.rpc.prepareObject(params))); if (result !== undefined) { if (async) { return jQuery.when(result); } else { return result; } } } var timeoutID = Sao.common.processing.show(); const id_ = Sao.rpc.id++; var ajax_success = function(data, status_, query) { if (data === null) { Sao.common.warning.run('', Sao.i18n.gettext('Unable to reach the server.')) .always(dfd.reject); } else if (data.id != id_) { Sao.common.warning.run('', Sao.i18n.gettext( `Invalid response id (${data.id}) expected ${id_}`)) .always(dfd.reject); } else if (data.error && !process_exception) { console.debug(`RPC error calling ${args}: ${data.error[0]}: ${data.error[1]}.`); dfd.reject(data.error); } else if (data.error && process_exception) { var name, msg, description; if (data.error[0] == 'UserWarning') { name = data.error[1][0]; msg = data.error[1][1]; description = data.error[1][2]; Sao.common.userwarning.run(description, msg) .then(function(result) { if (!~['always', 'ok'].indexOf(result)) { dfd.reject(); return; } Sao.rpc({ 'method': 'model.res.user.warning.skip', 'params': [name, result == 'always', {}], }, session).done(function() { if (async) { Sao.rpc(args, session).then( dfd.resolve, dfd.reject); } else { dfd.resolve(); } }); }, dfd.reject); } else if (data.error[0] == 'UserError') { msg = data.error[1][0]; description = data.error[1][1]; var domain = data.error[1][2]; if (!jQuery.isEmptyObject(domain)) { var fields = domain[1]; domain = domain[0]; var domain_parser = new Sao.common.DomainParser(fields); if (domain_parser.stringable(domain)) { description += '\n' + domain_parser.string(domain); } } Sao.common.warning.run(description, msg) .always(dfd.reject); } else if (data.error[0] == 'ConcurrencyException') { if (async && args.method.startsWith('model.') && (args.method.endsWith('.write') || args.method.endsWith('.delete')) && (args.params[0].length == 1)) { var model = args.method.split('.').slice(1, -1).join('.'); Sao.common.concurrency.run(model, args.params[0][0], args.params.slice(-1)[0]) .then(function() { delete args.params.slice(-1)[0]._timestamp; Sao.rpc(args, session).then( dfd.resolve, dfd.reject); }, dfd.reject); } else { Sao.common.message.run('Concurrency Exception', 'tryton-warning').always(dfd.reject); } } else { Sao.common.error.run(data.error[0], data.error[1]) .always(() => dfd.reject(data.error)); } } else { result = data.result; if (session.cache) { var cache = query.getResponseHeader('X-Tryton-Cache'); if (cache) { cache = parseInt(cache, 10); session.cache.set( args.method, JSON.stringify(Sao.rpc.prepareObject(params)), cache, result); } } dfd.resolve(result); } }; var ajax_error = function(query, status_, error) { if (query.status == 503) { this.retries++; if (this.retries < 5) { var timeoutID = Sao.common.processing.show(0); var delay = parseInt( query.getResponseHeader('Retry-After'), 10); if (isNaN(delay)) { delay = this.retries; } delay = Math.min(delay, 10); if (async) { window.setTimeout(() => { Sao.common.processing.hide(timeoutID); jQuery.ajax(this); }, 1000 * delay); } else { var start = new Date().getTime(); while (new Date().getTime() < start + 1000 * delay) { continue; } Sao.common.processing.hide(timeoutID); return jQuery.ajax(this); } return; } } else if (query.status == 401) { //Try to relog Sao.Session.renew(session).then(function() { if (async) { Sao.rpc(args, session, async, process_exception) .then(dfd.resolve, dfd.reject); } else { dfd.resolve(); } }, dfd.reject); } else if (!process_exception) { console.debug(`RPC error calling ${args}: ${status_}: ${error}.`); dfd.reject(); return; } else { var err_msg = `[${query.status}] ${error}`; Sao.common.message.run( Sao.i18n.gettext('Error: "%1". Try again later.', err_msg), 'tryton-error').always(dfd.reject); console.error(err_msg, error); } }; jQuery.ajax({ 'async': async, 'contentType': 'application/json', 'data': JSON.stringify(Sao.rpc.prepareObject({ 'id': id_, 'method': args.method, 'params': params })), 'dataType': 'json', 'url': (session.database || '.') + '/rpc/#' + args.method, 'type': 'post', 'complete': [function() { Sao.common.processing.hide(timeoutID); }], 'success': ajax_success, 'error': ajax_error, 'retries': 0, }); if (async) { return dfd.promise(); } else if (result === undefined) { throw dfd; } else { return result; } }; Sao.rpc.id = 0; Sao.rpc.convertJSONObject = function(value, index, parent) { if (value instanceof Array) { for (var i = 0, length = value.length; i < length; i++) { Sao.rpc.convertJSONObject(value[i], i, value); } } else if ((typeof(value) != 'string') && (typeof(value) != 'number') && (value !== null)) { if (value && value.__class__) { switch (value.__class__) { case 'datetime': value = Sao.DateTime(value.year, value.month - 1, value.day, value.hour, value.minute, value.second, Math.ceil(value.microsecond / 1000), true); break; case 'date': value = Sao.Date(value.year, value.month - 1, value.day); break; case 'time': value = Sao.Time(value.hour, value.minute, value.second, Math.ceil(value.microsecond / 1000)); break; case 'timedelta': value = Sao.TimeDelta(null, value.seconds); break; case 'bytes': value = Sao.common.atob(value); break; case 'Decimal': value = new Sao.Decimal(value.decimal); break; } if (parent) { parent[index] = value; } } else { for (var p in value) { Sao.rpc.convertJSONObject(value[p], p, value); } } } return parent || value; }; Sao.rpc.prepareObject = function(value, index, parent) { if (value instanceof Array) { value = jQuery.extend([], value); for (var i = 0, length = value.length; i < length; i++) { Sao.rpc.prepareObject(value[i], i, value); } } else if ((typeof(value) != 'string') && (typeof(value) != 'number') && (typeof(value) != 'boolean') && (value !== null) && (value !== undefined)) { if (value.isDate){ value = { '__class__': 'date', 'year': value.year(), 'month': value.month() + 1, 'day': value.date() }; } else if (value.isDateTime) { value = value.clone(); value = { '__class__': 'datetime', 'year': value.utc().year(), 'month': value.utc().month() + 1, 'day': value.utc().date(), 'hour': value.utc().hour(), 'minute': value.utc().minute(), 'second': value.utc().second(), 'microsecond': value.utc().millisecond() * 1000 }; } else if (value.isTime) { value = { '__class__': 'time', 'hour': value.hour(), 'minute': value.minute(), 'second': value.second(), 'microsecond': value.millisecond() * 1000 }; } else if (value.isTimeDelta) { value = { '__class__': 'timedelta', 'seconds': value.asSeconds() }; } else if (value instanceof Sao.Decimal) { value = { '__class__': 'Decimal', 'decimal': value.toString() }; } else if (value instanceof Uint8Array) { value = { '__class__': 'bytes', 'base64': Sao.common.btoa(value), }; } else { value = jQuery.extend({}, value); for (var p in value) { Sao.rpc.prepareObject(value[p], p, value); } } } if (parent) { parent[index] = value; } return parent || value; }; jQuery.ajaxSetup({ converters: { 'text json': function(json) { return Sao.rpc.convertJSONObject(jQuery.parseJSON(json)); } } }); }());