Commit 2db25474 authored by Romain Courteaud's avatar Romain Courteaud

ReplicateStorage: use stable data hash

JSON.stringify is not stable by design and can not be used to generate the hash.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

This commit adds a stable version of JSON.stringify, available at jIO.util.stringify.
Relying on the nearly same string representation will simplify compatibility.
parent fcb5c804
......@@ -110,6 +110,38 @@
}
util.readBlobAsDataURL = readBlobAsDataURL;
function stringify(obj) {
// Implement a stable JSON.stringify
// Object's keys are alphabetically ordered
var key,
key_list,
i,
value,
result_list;
if (obj.constructor === Object) {
key_list = Object.keys(obj).sort();
result_list = [];
for (i = 0; i < key_list.length; i += 1) {
key = key_list[i];
value = stringify(obj[key]);
if (value !== undefined) {
result_list.push(stringify(key) + ':' + value);
}
}
return '{' + result_list.join(',') + '}';
}
if (obj.constructor === Array) {
result_list = [];
for (i = 0; i < obj.length; i += 1) {
result_list.push(stringify(obj[i]));
}
return '[' + result_list.join(',') + ']';
}
return JSON.stringify(obj);
}
util.stringify = stringify;
// https://gist.github.com/davoclavo/4424731
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
......
......@@ -19,7 +19,7 @@
/*jslint nomen: true*/
/*global jIO, RSVP, Rusha*/
(function (jIO, RSVP, Rusha) {
(function (jIO, RSVP, Rusha, stringify) {
"use strict";
var rusha = new Rusha(),
......@@ -46,9 +46,9 @@
this._remote_sub_storage = jIO.createJIO(spec.remote_sub_storage);
this._signature_hash = "_replicate_" + generateHash(
JSON.stringify(spec.local_sub_storage) +
JSON.stringify(spec.remote_sub_storage) +
JSON.stringify(this._query_options)
stringify(spec.local_sub_storage) +
stringify(spec.remote_sub_storage) +
stringify(this._query_options)
);
this._signature_sub_storage = jIO.createJIO({
type: "document",
......@@ -213,14 +213,14 @@
return getMethod(id);
})
.push(function (doc) {
var local_hash = generateHash(JSON.stringify(doc)),
var local_hash = generateHash(stringify(doc)),
remote_hash;
if (remote_doc === undefined) {
return propagateModification(source, destination, doc, local_hash,
id, options);
}
remote_hash = generateHash(JSON.stringify(remote_doc));
remote_hash = generateHash(stringify(remote_doc));
if (local_hash === remote_hash) {
// Same document
return context._signature_sub_storage.put(id, {
......@@ -239,8 +239,8 @@
}
// Already exists on destination
throw new jIO.util.jIOError("Conflict on '" + id + "': " +
JSON.stringify(doc) + " !== " +
JSON.stringify(remote_doc),
stringify(doc) + " !== " +
stringify(remote_doc),
409);
});
}
......@@ -283,7 +283,7 @@
status_hash = result.hash;
return destination.get(id)
.push(function (doc) {
var remote_hash = generateHash(JSON.stringify(doc));
var remote_hash = generateHash(stringify(doc));
if (remote_hash === status_hash) {
return destination.remove(id)
.push(function () {
......@@ -322,14 +322,14 @@
})
.push(function (result_list) {
var doc = result_list[0],
local_hash = generateHash(JSON.stringify(doc)),
local_hash = generateHash(stringify(doc)),
status_hash = result_list[1].hash;
if (local_hash !== status_hash) {
// Local modifications
return destination.get(id)
.push(function (remote_doc) {
var remote_hash = generateHash(JSON.stringify(remote_doc));
var remote_hash = generateHash(stringify(remote_doc));
if (remote_hash !== status_hash) {
// Modifications on both sides
if (local_hash === remote_hash) {
......@@ -346,8 +346,8 @@
}
if (conflict_force !== true) {
throw new jIO.util.jIOError("Conflict on '" + id + "': " +
JSON.stringify(doc) + " !== " +
JSON.stringify(remote_doc),
stringify(doc) + " !== " +
stringify(remote_doc),
409);
}
}
......@@ -570,4 +570,4 @@
jIO.addStorage('replicate', ReplicateStorage);
}(jIO, RSVP, Rusha));
}(jIO, RSVP, Rusha, jIO.util.stringify));
......@@ -104,7 +104,7 @@
equal(jio.__storage._check_remote_modification, false);
equal(jio.__storage._signature_hash,
"_replicate_623653d45a4e770a2c9f6b71e3144d18ee1b5bec");
"_replicate_11881e431308c0ec8c0e6430be98db380e1b92f8");
});
test("reject unknow conflict resolution", function () {
......@@ -2062,7 +2062,7 @@
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " +
"_replicate_e0cd4a29dc7c74a9de1d7a9cdbfcbaa776863d67");
"_replicate_8662994dcefb3a2ceec61e86953efda8ec6520d6");
equal(error.status_code, 404);
})
.then(function () {
......@@ -2076,7 +2076,7 @@
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " +
"_replicate_e0cd4a29dc7c74a9de1d7a9cdbfcbaa776863d67");
"_replicate_8662994dcefb3a2ceec61e86953efda8ec6520d6");
equal(error.status_code, 404);
})
.fail(function (error) {
......
(function (jIO, QUnit) {
"use strict";
var test = QUnit.test,
equal = QUnit.equal,
module = QUnit.module;
/////////////////////////////////////////////////////////////////
// util.stringify
/////////////////////////////////////////////////////////////////
module("util.stringify");
test("is stable", function () {
var str = jIO.util.stringify;
// https://developer.mozilla.org/search?q=stringify
equal(str({}), '{}');
equal(str(true), 'true');
equal(str('foo'), '"foo"');
equal(str([1, 'false', false]), '[1,"false",false]');
equal(str({ x: 5 }), '{"x":5}');
equal(str(new Date(Date.UTC(2006, 0, 2, 15, 4, 5))),
'"2006-01-02T15:04:05.000Z"');
equal(str({ x: 5, y: 6, z: 7 }), '{"x":5,"y":6,"z":7}');
equal(str({ z: 7, y: 6, x: 5 }), '{"x":5,"y":6,"z":7}');
equal(str(Object.create(null, { x: { value: 'x', enumerable: false },
y: { value: 'y', enumerable: true } })),
'{"y":"y"}');
});
}(jIO, QUnit));
\ No newline at end of file
......@@ -17,6 +17,7 @@
<!--script src="jio/util.js"></script-->
<!--script src="jio/fakestorage.js"></script>
<script src="jio/tests.js"></script-->
<script src="jio/util.js"></script>
<script src="queries/key.tests.js"></script>
<script src="queries/key-schema.tests.js"></script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment