Skip to content
This repository was archived by the owner on Dec 5, 2019. It is now read-only.

Commit 1eac83d

Browse files
committed
Diff Sync - Initial Addition of Diff/Sync Client - AGJS-124
1 parent 967f52e commit 1eac83d

17 files changed

+3637
-4
lines changed

.jshintrc

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
"Paho": true,
2121
"File": true,
2222
"sjcl": true,
23-
"crypto": true
23+
"crypto": true,
24+
"diff_match_patch": true,
25+
"jsonpatch": true
2426
}
2527
}

Gruntfile.js

+25-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module.exports = function(grunt) {
3232
banner: '<%= meta.banner %>'
3333
},
3434
dist: {
35-
src: ['src/aerogear.core.js', 'external/uuid/uuid.js', 'external/crypto/sjcl.js', 'src/data-manager/aerogear.datamanager.js', 'src/data-manager/adapters/base.js', 'src/data-manager/adapters/memory.js', 'src/data-manager/adapters/session-local.js', 'src/data-manager/adapters/indexeddb.js', 'src/data-manager/adapters/websql.js', 'src/notifier/aerogear.notifier.js', 'src/notifier/adapters/base.js', 'src/notifier/adapters/simplePush.js', 'src/notifier/adapters/vertx.js', 'src/notifier/adapters/stompws.js', 'src/notifier/adapters/mqttws.js', 'src/unifiedpush/aerogear.unifiedpush.js', 'src/simplepush/aerogear.simplepush.js', 'src/crypto/aerogear.crypto.js'],
35+
src: ['src/aerogear.core.js', 'external/uuid/uuid.js', 'external/crypto/sjcl.js', 'src/data-manager/aerogear.datamanager.js', 'src/data-manager/adapters/base.js', 'src/data-manager/adapters/memory.js', 'src/data-manager/adapters/session-local.js', 'src/data-manager/adapters/indexeddb.js', 'src/data-manager/adapters/websql.js', 'src/notifier/aerogear.notifier.js', 'src/notifier/adapters/base.js', 'src/notifier/adapters/simplePush.js', 'src/notifier/adapters/vertx.js', 'src/notifier/adapters/stompws.js', 'src/notifier/adapters/mqttws.js', 'src/unifiedpush/aerogear.unifiedpush.js', 'src/simplepush/aerogear.simplepush.js', 'src/crypto/aerogear.crypto.js', 'src/diff-sync/aerogear.diff-sync-engine.js', 'src/diff-sync/engine-adapters/diff-match-patch.js', 'src/diff-sync/engine-adapters/json-patch.js', 'src/diff-sync/aerogear.diff-sync-client.js'],
3636
dest: 'dist/<%= pkg.name %>.js'
3737
},
3838
dataManager: {
@@ -94,14 +94,30 @@ module.exports = function(grunt) {
9494
src: ['src/aerogear.core.js', 'external/crypto/sjcl.js', 'src/crypto/aerogear.crypto.js'],
9595
description: 'Crypto build',
9696
dest: 'dist/<%= pkg.name %>.custom.js'
97+
},
98+
diffSync: {
99+
src: ['src/aerogear.core.js', 'src/diff-sync/aerogear.diff-sync-engine.js', 'src/diff-sync/engine-adapters/diff-match-patch.js', 'src/diff-sync/engine-adapters/json-patch.js', 'src/diff-sync/aerogear.diff-sync-client.js'],
100+
description: 'Differential Sync Client and Engine',
101+
dest: 'dist/<%= pkg.name %>.custom.js'
102+
},
103+
diffSyncDiffMatchPatch: {
104+
src: ['src/aerogear.core.js', 'src/diff-sync/aerogear.diff-sync-engine.js', 'src/diff-sync/engine-adapters/diff-match-patch.js', 'src/diff-sync/aerogear.diff-sync-client.js'],
105+
description: 'Differential Sync Client and Engine - Diff Match Patch Only',
106+
dest: 'dist/<%= pkg.name %>.custom.js'
107+
},
108+
diffSyncJsonPatch: {
109+
src: ['src/aerogear.core.js', 'src/diff-sync/aerogear.diff-sync-engine.js', 'src/diff-sync/engine-adapters/json-patch.js', 'src/diff-sync/aerogear.diff-sync-client.js'],
110+
description: 'Differential Sync Client and Engine - JSON Patch Only',
111+
dest: 'dist/<%= pkg.name %>.custom.js'
97112
}
98113
},
99114
qunit: {
100115
dataManager: ['tests/unit/data-manager/**/*.html', 'tests/unit/data-manager-websql/**/*.html'],
101116
notifier: 'tests/unit/notifier/**/*.html',
102117
crypto: 'tests/unit/crypto/**/*.html',
103118
unifiedpush: 'tests/unit/unifiedpush/**/*.html',
104-
simplepush: 'tests/unit/simplepush/**/*.html'
119+
simplepush: 'tests/unit/simplepush/**/*.html',
120+
diffSync: 'tests/unit/sync/**/*.html'
105121
},
106122
jshint: {
107123
all: {
@@ -171,6 +187,10 @@ module.exports = function(grunt) {
171187
core: {
172188
files: 'src/aerogear.core.js',
173189
tasks: 'qunit'
190+
},
191+
diffSync: {
192+
files: 'src/diff-sync/**/*.js',
193+
tasks: 'qunit:sync'
174194
}
175195
},
176196
ci: {
@@ -217,6 +237,9 @@ module.exports = function(grunt) {
217237
grunt.registerTask('unifiedPush', ['concat:unifiedPush']);
218238
grunt.registerTask('push', ['concat:push']);
219239
grunt.registerTask('crypto', ['concat:crypto']);
240+
grunt.registerTask('diffSync', ['concat:diffSync']);
241+
grunt.registerTask('diffSyncDiffMatchPatch', ['concat:diffSyncDiffMatchPatch']);
242+
grunt.registerTask('diffSyncJsonPatch', ['concat:diffSyncJsonPatch']);
220243
grunt.registerTask('travis', ['jshint', 'qunit', 'concat:dist', 'setupCi', 'ci']);
221244

222245
grunt.registerTask('docs', function() {

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ SimplePushClient is a client implementation and polyfill for the Mozilla SimpleP
5656

5757
See the [SimplePushClient API docs](http://aerogear.org/docs/specs/aerogear-js/AeroGear.SimplePushClient.html) for more info. Also, please see the [Mozilla SimplePush specification](https://wiki.mozilla.org/WebAPI/SimplePush) for more info on SimplePush.
5858

59+
## Diff Sync
60+
- - -
61+
62+
The Diff Sync client and server are based on an implementation of Google's [Differential Synchonrization](http://research.google.com/pubs/pub35605.html) by Neil Fraser.
63+
64+
The DiffSyncClient connects to the [AeroGear Sync Server](https://github.com/aerogear/aerogear-sync-server)
65+
66+
The DiffSyncEngine is responsible for the algorithm logic.
67+
68+
5969
## UnifiedPushClient
6070
- - -
6171

@@ -110,6 +120,13 @@ Some parts of AeroGear.js depend on external libraries which are not bundled in
110120
### UnifiedPush
111121
* [ES6 Promise polyfill](https://github.com/jakearchibald/es6-promise)
112122

123+
### Diff Sync
124+
* **Diff Match Patch**
125+
* [Google Diff Match Patch](https://code.google.com/p/google-diff-match-patch/)
126+
127+
* **JSON Patch**
128+
* [JSON Patch](https://github.com/Starcounter-Jack/JSON-Patch)
129+
113130
## Building
114131
- - -
115132

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/* AeroGear JavaScript Library
2+
* https://github.com/aerogear/aerogear-js
3+
* JBoss, Home of Professional Open Source
4+
* Copyright Red Hat, Inc., and individual contributors
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
/**
17+
The AeroGear Differential Sync Client.
18+
@status Experimental
19+
@constructs AeroGear.DiffSyncClient
20+
@param {Object} config - A configuration
21+
@param {String} config.serverUrl - the url of the Differential Sync Server
22+
@param {Object} [config.syncEngine="AeroGear.DiffSyncEngine"] -
23+
@param {function} [config.onopen] - will be called when a connection to the sync server has been opened
24+
@param {function} [config.onclose] - will be called when a connection to the sync server has been closed
25+
@param {function} [config.onsync] - listens for "sync" events from the sync server
26+
@param {function} [config.onerror] - will be called when there are errors from the sync server
27+
@returns {object} diffSyncClient - The created DiffSyncClient
28+
*/
29+
AeroGear.DiffSyncClient = function ( config ) {
30+
if ( ! ( this instanceof AeroGear.DiffSyncClient ) ) {
31+
return new AeroGear.DiffSyncClient( config );
32+
}
33+
34+
config = config || {};
35+
36+
var ws,
37+
sendQueue = [],
38+
that = this,
39+
syncEngine = config.syncEngine || new AeroGear.DiffSyncEngine({name: 'jsonPatchEngine'}).engines.jsonPatchEngine;
40+
41+
if ( config.serverUrl === undefined ) {
42+
throw new Error( "'config.serverUrl' must be specified" );
43+
}
44+
45+
/**
46+
Connects to the Differential Sync Server using WebSockets
47+
*/
48+
this.connect = function() {
49+
ws = new WebSocket( config.serverUrl );
50+
ws.onopen = function ( e ) {
51+
if ( config.onopen ) {
52+
config.onopen.apply( this, arguments );
53+
}
54+
55+
while ( sendQueue.length ) {
56+
var task = sendQueue.pop();
57+
if ( task.type === "add" ) {
58+
send ( task.type, task.msg );
59+
} else {
60+
that.sendEdits( task.msg );
61+
}
62+
}
63+
};
64+
ws.onmessage = function( e ) {
65+
var data, doc;
66+
67+
try {
68+
data = JSON.parse( e.data );
69+
} catch( err ) {
70+
data = {};
71+
}
72+
73+
if ( data ) {
74+
that._patch( data );
75+
}
76+
77+
doc = that.getDocument( data.id );
78+
79+
if( config.onsync ) {
80+
config.onsync.call( this, doc, e );
81+
}
82+
};
83+
ws.onerror = function( e ) {
84+
if ( config.onerror ) {
85+
config.onerror.apply( this, arguments );
86+
}
87+
};
88+
ws.onclose = function( e ) {
89+
if ( config.onclose ) {
90+
config.onclose.apply( this, arguments);
91+
}
92+
};
93+
};
94+
95+
// connect needs to be callable for implementing reconnect.
96+
this.connect();
97+
98+
/**
99+
Disconnects from the Differential Sync Server closing it's Websocket connection
100+
*/
101+
this.disconnect = function() {
102+
ws.close();
103+
};
104+
105+
/**
106+
patch - an internal method to sync the data with the Sync Engine
107+
@param {Object} data - The data to be patched
108+
*/
109+
this._patch = function( data ) {
110+
syncEngine.patch( data );
111+
};
112+
113+
/**
114+
getDocument - gets the document from the Sync Engine
115+
@param {String} id - the id of the document to get
116+
@returns {Object} - The document from the sync engine
117+
*/
118+
this.getDocument = function( id ) {
119+
return syncEngine.getDocument( id );
120+
};
121+
122+
/**
123+
diff - an internal method to perform a diff with the Sync Server
124+
@param {Object} data - the data to perform a diff on
125+
@returns {Object} - An Object containing the edits from the Sync Engine
126+
*/
127+
this._diff = function( data ) {
128+
return syncEngine.diff( data );
129+
};
130+
131+
/**
132+
addDocument - Adds a document to the Sync Engine
133+
@param {Object} doc - a document to add to the sync engine
134+
*/
135+
this.addDocument = function( doc ) {
136+
syncEngine.addDocument( doc );
137+
138+
if ( ws.readyState === 0 ) {
139+
sendQueue.push( { type: "add", msg: doc } );
140+
} else if ( ws.readyState === 1 ) {
141+
send( "add", doc );
142+
}
143+
};
144+
145+
/**
146+
sendEdits - an internal method to send the edits from the Sync Engine to the Sync Server
147+
@param {Object} edit - the edits to be sent to the server
148+
*/
149+
this._sendEdits = function( edit ) {
150+
if ( ws.readyState === WebSocket.OPEN ) {
151+
//console.log( 'sending edits:', edit );
152+
ws.send( JSON.stringify( edit ) );
153+
} else {
154+
//console.log("Client is not connected. Add edit to queue");
155+
if ( sendQueue.length === 0 ) {
156+
sendQueue.push( { type: "patch", msg: edit } );
157+
} else {
158+
var updated = false;
159+
for (var i = 0 ; i < sendQueue.length; i++ ) {
160+
var task = sendQueue[i];
161+
if (task.type === "patch" && task.msg.clientId === edit.clientId && task.msg.id === edit.id) {
162+
for (var j = 0 ; j < edit.edits.length; j++) {
163+
task.msg.edits.push( edit.edits[j] );
164+
}
165+
updated = true;
166+
}
167+
}
168+
if ( !updated ) {
169+
sendQueue.push( { type: "patch", msg: edit } );
170+
}
171+
}
172+
}
173+
};
174+
175+
/**
176+
sync - performs the Sync process
177+
@param {Object} data - the Data to be sync'd with the server
178+
*/
179+
this.sync = function( data ) {
180+
var edits = that._diff( data );
181+
that._sendEdits( edits );
182+
};
183+
184+
/**
185+
removeDoc
186+
TODO
187+
*/
188+
this.removeDoc = function( doc ) {
189+
throw new Error( "Method Not Yet Implemented" );
190+
};
191+
192+
/**
193+
fetch - fetch a document from the Sync Server. Will perform a sync on it
194+
@param {String} docId - the id of a document to fetch from the Server
195+
*/
196+
this.fetch = function( docId ) {
197+
var doc, edits, task;
198+
199+
if ( sendQueue.length === 0 ) {
200+
doc = syncEngine.getDocument( docId );
201+
that.sync( doc );
202+
} else {
203+
while ( sendQueue.length ) {
204+
task = sendQueue.shift();
205+
if ( task.type === "add" ) {
206+
send ( task.type, task.msg );
207+
} else {
208+
that._sendEdits( task.msg );
209+
}
210+
}
211+
}
212+
};
213+
214+
/**
215+
send
216+
@param {String} msgType
217+
@param {Object} doc
218+
*/
219+
var send = function ( msgType, doc ) {
220+
var json = { msgType: msgType, id: doc.id, clientId: doc.clientId, content: doc.content };
221+
//console.log ( 'sending ' + JSON.stringify ( json ) );
222+
ws.send( JSON.stringify ( json ) );
223+
};
224+
};
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* AeroGear JavaScript Library
2+
* https://github.com/aerogear/aerogear-js
3+
* JBoss, Home of Professional Open Source
4+
* Copyright Red Hat, Inc., and individual contributors
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
/**
17+
The AeroGear Differential Sync Engine.
18+
@status Experimental
19+
@constructs AeroGear.DiffSyncEngine
20+
@param {Object} config - A configuration
21+
@param {function} [config.type = "jsonPatch"] - the type of sync engine, defaults to jsonPatch
22+
@returns {object} diffSyncClient - The created DiffSyncClient
23+
*/
24+
AeroGear.DiffSyncEngine = function( config ) {
25+
if ( !( this instanceof AeroGear.DiffSyncEngine ) ) {
26+
return new AeroGear.DiffSyncEngine( config );
27+
}
28+
// Super Constructor
29+
AeroGear.Core.call( this );
30+
31+
this.lib = "DiffSyncEngine";
32+
this.type = config ? config.type || "jsonPatch" : "jsonPatch";
33+
34+
/**
35+
The name used to reference the collection of sync engines instances created from the adapters
36+
@memberOf AeroGear.DiffSyncEngine
37+
@type Object
38+
@default modules
39+
*/
40+
this.collectionName = "engines";
41+
42+
this.add( config );
43+
};
44+
45+
AeroGear.DiffSyncEngine.prototype = AeroGear.Core;
46+
AeroGear.DiffSyncEngine.constructor = AeroGear.DiffSyncEngine;
47+
48+
/**
49+
The adapters object is provided so that adapters can be added to the AeroGear.DiffSyncEngine namespace dynamically and still be accessible to the add method
50+
@augments AeroGear.DiffSyncEngine
51+
*/
52+
AeroGear.DiffSyncEngine.adapters = {};

0 commit comments

Comments
 (0)