UNPKG

wed

Version:

Wed is a schema-aware editor for XML documents.

224 lines 8.89 kB
/** * Data saving functionality, using Ajax. * * @author Louis-Dominique Dubeau * @license MPL 2.0 * @copyright Mangalam Research Center for Buddhist Languages */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; define(["require", "exports", "merge-options", "wed"], function (require, exports, merge_options_1, wed_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); merge_options_1 = __importDefault(merge_options_1); /** * Processes a list of messages received from the server. * * @param data The data received from the server. * * @returns An object which has for field names message types and for field * values a message of the corresponding type. * * @throws {Error} If there is more than one message of the same type in the * data being processed. */ function getMessages(data) { const raw = data.messages; if (raw === undefined || raw.length === 0) { return undefined; } const ret = {}; for (const msg of raw) { // When we parse responses from the server it is not possible to get an // answer for which msg.type is undefined. const errType = msg.type; if (ret[errType] !== undefined) { throw new Error("same type of message appearing more than " + "once in one transaction"); } ret[errType] = msg; } return ret; } /** * A saver responsible for communicating with a server to save the data edited * by a wed editor. * * @param runtime The runtime under which this saver is created. * * @param version The version of wed for which this object is created. * * @param dataUpdater The updater that the editor created for its data tree. * * @param {Node} dataTree The editor's data tree. * * @param options The options specific to this class. */ class AjaxSaver extends wed_1.saver.Saver { constructor(runtime, version, dataUpdater, dataTree, options) { super(runtime, version, dataUpdater, dataTree, options); const headers = options.headers; this.headers = headers != null ? headers : {}; // This value is saved with the double quotes around it so that we can just // pass it to 'If-Match'. const initial_etag = options.initial_etag; this.etag = initial_etag != null ? `"${initial_etag}"` : undefined; this.url = options.url; // Every 5 minutes. this.setAutosaveInterval(5 * 60 * 1000); } init() { return this._post({ command: "check", version: this.version }, "json") .then(() => { this.initialized = true; this.failed = false; }) .catch(() => { // This effectively aborts the editing session. This is okay, since // there's a good chance that the issue is major. throw new Error(`${this.url} is not responding to a check; \ saving is not possible.`); }); } _save(autosave) { return Promise.resolve().then(() => { if (!this.initialized) { return; } // We must store this value now because a modifying operation could occur // after the data is sent to the server but before we can be sure the data // is saved. const savingGeneration = this.currentGeneration; let ignore = false; return this._post({ command: autosave ? "autosave" : "save", version: this.version, data: this.getData(), }, "json") .catch(() => { ignore = true; const error = { msg: "Your browser cannot contact the server", type: "save_disconnected", }; this._fail(error); return { messages: [] }; }) .then((data) => { if (ignore) { return; } const msgs = getMessages(data); if (msgs === undefined) { this._fail(); throw new Error(`The server accepted the save request but did \ not return any information regarding whether the save was successful or not.`); } if (msgs.save_fatal_error !== undefined) { this._fail(); throw new Error(`The server was not able to save the data due to \ a fatal error. Please contact technical support before trying to edit again.`); } if (msgs.save_transient_error !== undefined) { this._events.next({ name: "Failed", error: msgs.save_transient_error }); return; } if (msgs.save_edited !== undefined) { this._fail(msgs.save_edited); return; } if (msgs.save_successful === undefined) { this._fail(); throw new Error(`Unexpected response from the server while \ saving. Please contact technical support before trying to edit again.`); } if (msgs.version_too_old_error !== undefined) { this._fail({ type: "too_old", msg: "" }); return; } this._saveSuccess(autosave, savingGeneration); }); }); } _recover() { const success = (data) => { const msgs = getMessages(data); if (msgs === undefined) { return false; } if (msgs.save_fatal_error !== undefined) { return false; } if (msgs.save_successful === undefined) { return false; } return true; }; return this._post({ command: "recover", version: this.version, data: this.getData(), }, "json") .then(success) .catch(() => false); } /** * Utility wrapper for Ajax queries. Read the code for more information. * * @private * * @param data * @param dataType * * @returns A promise that resolves when the post is over. */ _post(data, dataType) { let headers; if (this.etag !== undefined) { headers = merge_options_1.default(this.headers, { "If-Match": this.etag, }); } else { headers = this.headers; } return this.runtime.ajax({ type: "POST", url: this.url, data: data, dataType: dataType, headers: headers, bluejaxOptions: { verboseResults: true, }, }).then(([reply, , jqXHR]) => { const msgs = getMessages(reply); // Unsuccessful operations don't have a valid etag. if (msgs !== undefined && msgs.save_successful !== undefined) { this.etag = jqXHR.getResponseHeader("ETag"); } return reply; // tslint:disable-next-line:no-any }).catch((bluejaxError) => { const jqXHR = bluejaxError.jqXHR; // This is a case where a precondition failed. if (jqXHR.status === 412) { // We transform the 412 status into a Response object that will // produce the right reaction. return { messages: [{ msg: "The document was edited by someone else.", type: "save_edited", }], }; } throw bluejaxError; }); } } exports.Saver = AjaxSaver; }); // LocalWords: MPL ETag runtime etag json url autosave //# sourceMappingURL=ajax.js.map