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 @@ ...@@ -110,6 +110,38 @@
} }
util.readBlobAsDataURL = readBlobAsDataURL; 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 // https://gist.github.com/davoclavo/4424731
function dataURItoBlob(dataURI) { function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string // convert base64 to raw binary data held in a string
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
/*jslint nomen: true*/ /*jslint nomen: true*/
/*global jIO, RSVP, Rusha*/ /*global jIO, RSVP, Rusha*/
(function (jIO, RSVP, Rusha) { (function (jIO, RSVP, Rusha, stringify) {
"use strict"; "use strict";
var rusha = new Rusha(), var rusha = new Rusha(),
...@@ -46,9 +46,9 @@ ...@@ -46,9 +46,9 @@
this._remote_sub_storage = jIO.createJIO(spec.remote_sub_storage); this._remote_sub_storage = jIO.createJIO(spec.remote_sub_storage);
this._signature_hash = "_replicate_" + generateHash( this._signature_hash = "_replicate_" + generateHash(
JSON.stringify(spec.local_sub_storage) + stringify(spec.local_sub_storage) +
JSON.stringify(spec.remote_sub_storage) + stringify(spec.remote_sub_storage) +
JSON.stringify(this._query_options) stringify(this._query_options)
); );
this._signature_sub_storage = jIO.createJIO({ this._signature_sub_storage = jIO.createJIO({
type: "document", type: "document",
...@@ -213,14 +213,14 @@ ...@@ -213,14 +213,14 @@
return getMethod(id); return getMethod(id);
}) })
.push(function (doc) { .push(function (doc) {
var local_hash = generateHash(JSON.stringify(doc)), var local_hash = generateHash(stringify(doc)),
remote_hash; remote_hash;
if (remote_doc === undefined) { if (remote_doc === undefined) {
return propagateModification(source, destination, doc, local_hash, return propagateModification(source, destination, doc, local_hash,
id, options); id, options);
} }
remote_hash = generateHash(JSON.stringify(remote_doc)); remote_hash = generateHash(stringify(remote_doc));
if (local_hash === remote_hash) { if (local_hash === remote_hash) {
// Same document // Same document
return context._signature_sub_storage.put(id, { return context._signature_sub_storage.put(id, {
...@@ -239,8 +239,8 @@ ...@@ -239,8 +239,8 @@
} }
// Already exists on destination // Already exists on destination
throw new jIO.util.jIOError("Conflict on '" + id + "': " + throw new jIO.util.jIOError("Conflict on '" + id + "': " +
JSON.stringify(doc) + " !== " + stringify(doc) + " !== " +
JSON.stringify(remote_doc), stringify(remote_doc),
409); 409);
}); });
} }
...@@ -283,7 +283,7 @@ ...@@ -283,7 +283,7 @@
status_hash = result.hash; status_hash = result.hash;
return destination.get(id) return destination.get(id)
.push(function (doc) { .push(function (doc) {
var remote_hash = generateHash(JSON.stringify(doc)); var remote_hash = generateHash(stringify(doc));
if (remote_hash === status_hash) { if (remote_hash === status_hash) {
return destination.remove(id) return destination.remove(id)
.push(function () { .push(function () {
...@@ -322,14 +322,14 @@ ...@@ -322,14 +322,14 @@
}) })
.push(function (result_list) { .push(function (result_list) {
var doc = result_list[0], var doc = result_list[0],
local_hash = generateHash(JSON.stringify(doc)), local_hash = generateHash(stringify(doc)),
status_hash = result_list[1].hash; status_hash = result_list[1].hash;
if (local_hash !== status_hash) { if (local_hash !== status_hash) {
// Local modifications // Local modifications
return destination.get(id) return destination.get(id)
.push(function (remote_doc) { .push(function (remote_doc) {
var remote_hash = generateHash(JSON.stringify(remote_doc)); var remote_hash = generateHash(stringify(remote_doc));
if (remote_hash !== status_hash) { if (remote_hash !== status_hash) {
// Modifications on both sides // Modifications on both sides
if (local_hash === remote_hash) { if (local_hash === remote_hash) {
...@@ -346,8 +346,8 @@ ...@@ -346,8 +346,8 @@
} }
if (conflict_force !== true) { if (conflict_force !== true) {
throw new jIO.util.jIOError("Conflict on '" + id + "': " + throw new jIO.util.jIOError("Conflict on '" + id + "': " +
JSON.stringify(doc) + " !== " + stringify(doc) + " !== " +
JSON.stringify(remote_doc), stringify(remote_doc),
409); 409);
} }
} }
...@@ -570,4 +570,4 @@ ...@@ -570,4 +570,4 @@
jIO.addStorage('replicate', ReplicateStorage); jIO.addStorage('replicate', ReplicateStorage);
}(jIO, RSVP, Rusha)); }(jIO, RSVP, Rusha, jIO.util.stringify));
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
equal(jio.__storage._check_remote_modification, false); equal(jio.__storage._check_remote_modification, false);
equal(jio.__storage._signature_hash, equal(jio.__storage._signature_hash,
"_replicate_623653d45a4e770a2c9f6b71e3144d18ee1b5bec"); "_replicate_11881e431308c0ec8c0e6430be98db380e1b92f8");
}); });
test("reject unknow conflict resolution", function () { test("reject unknow conflict resolution", function () {
...@@ -2062,7 +2062,7 @@ ...@@ -2062,7 +2062,7 @@
.fail(function (error) { .fail(function (error) {
ok(error instanceof jIO.util.jIOError); ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + equal(error.message, "Cannot find document: " +
"_replicate_e0cd4a29dc7c74a9de1d7a9cdbfcbaa776863d67"); "_replicate_8662994dcefb3a2ceec61e86953efda8ec6520d6");
equal(error.status_code, 404); equal(error.status_code, 404);
}) })
.then(function () { .then(function () {
...@@ -2076,7 +2076,7 @@ ...@@ -2076,7 +2076,7 @@
.fail(function (error) { .fail(function (error) {
ok(error instanceof jIO.util.jIOError); ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + equal(error.message, "Cannot find document: " +
"_replicate_e0cd4a29dc7c74a9de1d7a9cdbfcbaa776863d67"); "_replicate_8662994dcefb3a2ceec61e86953efda8ec6520d6");
equal(error.status_code, 404); equal(error.status_code, 404);
}) })
.fail(function (error) { .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 @@ ...@@ -17,6 +17,7 @@
<!--script src="jio/util.js"></script--> <!--script src="jio/util.js"></script-->
<!--script src="jio/fakestorage.js"></script> <!--script src="jio/fakestorage.js"></script>
<script src="jio/tests.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.tests.js"></script>
<script src="queries/key-schema.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