wed
Version:
Wed is a schema-aware editor for XML documents.
224 lines • 8.89 kB
JavaScript
/**
* 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