From 83fb55cc08d7a831dc3c394571ff4a472dff2d58 Mon Sep 17 00:00:00 2001 From: scottinet Date: Mon, 16 Nov 2015 18:06:46 +0100 Subject: [PATCH 1/4] added offline mode options --- package.json | 2 +- src/kuzzle.js | 73 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 53f5df338..e693026a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kuzzle-sdk", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "A connector for the Kuzzle API", "author": "The Kuzzle Team ", "repository": { diff --git a/src/kuzzle.js b/src/kuzzle.js index d9b85a433..82fabbd66 100644 --- a/src/kuzzle.js +++ b/src/kuzzle.js @@ -38,11 +38,16 @@ module.exports = Kuzzle = function (url, options, cb) { Object.defineProperties(this, { // 'private' properties + collections: { + value: {}, + writable: true + }, eventListeners: { value: { subscribed: [], unsubscribed: [], - disconnected: [] + disconnected: [], + reconnected: [] } }, requestHistory: { @@ -53,6 +58,10 @@ module.exports = Kuzzle = function (url, options, cb) { value: null, writable: true }, + state: { + value: 'initializing', + writable: true + }, subscriptions: { /* Contains the centralized subscription list in the following format: @@ -67,29 +76,54 @@ module.exports = Kuzzle = function (url, options, cb) { writable: true }, // read-only properties - collections: { - value: {}, + autoReconnect: { + value: (options && options.autoReconnect) ? options.autoReconnect : true, + enumerable: true + }, + reconnectionDelay: { + value: (options && options.reconnectionDelay) ? options.reconnectionDelay : 1000, + enumerable: true + }, + // writable properties + autoQueue: { + value: false, enumerable: true, writable: true }, - // writable properties - autoReconnect: { - value: (options && options.autoReconnect) ? options.autoReconnect : true, + autoReplay: { + value: false, + enumerable: true, + writable: true + }, + autoResubscribe: { + value: true, enumerable: true, writable: true }, headers: { - value: (options && options.headers) ? options. headers : {}, + value: {}, enumerable: true, writable: true }, metadata: { - value: (options && options.metadata) ? options.metadata : {}, + value: {}, enumerable: true, writable: true } }); + if (options) { + Object.keys(options).forEach(function (opt) { + if (this.hasOwnProperty(opt) && Object.getOwnPropertyDescriptor(this, opt).writable) { + this[opt] = options[opt]; + } + }); + + if (options.offlineMode === 'auto' && this.autoReconnect) { + this.autoQueue = this.autoReplay = this.autoResubscribe = true; + } + } + // Helper function ensuring that this Kuzzle object is still valid before performing a query Object.defineProperty(this, 'isValid', { value: function () { @@ -147,17 +181,19 @@ function construct(url, cb) { throw new Error('URL to Kuzzle can\'t be empty'); } - this.socket = io(url); + this.socket = io(url, {reconnection: this.autoReconnect, reconnectionDelay: this.reconnectionDelay}); this.socket.once('connect', function () { - // TODO: initialize kuzzle-provided properties (applicationId, connectionId, connectionTimestamp) + self.state = 'connected'; + if (cb) { cb(null, self); } }); this.socket.once('error', function (error) { - // TODO: Invalidate this object for now. Should handle the autoReconnect flag later + self.state = 'error'; + self.logout(); if (cb) { @@ -166,11 +202,25 @@ function construct(url, cb) { }); this.socket.on('disconnect', function () { + self.state = 'offline'; + + if (!self.autoReconnect) { + self.logout(); + } + self.eventListeners.disconnected.forEach(function (listener) { listener(); }); }); + this.socket.on('reconnect', function () { + self.state = 'connected'; + + self.eventListeners.reconnected.forEach(function (listener) { + listener(); + }); + }); + return this; } @@ -242,6 +292,7 @@ Kuzzle.prototype.getAllStatistics = function (cb) { * Kuzzle monitors active connections, and ongoing/completed/failed requests. * This method allows getting either the last statistics frame, or a set of frames starting from a provided timestamp. * + * @param {number} timestamp - Epoch time. Starting time from which the frames are to be retrieved * @param {responseCallback} cb - Handles the query response * @returns {object} this */ From 6e4eab99d9b9ddaaca949c191b98d6de7a6a159a Mon Sep 17 00:00:00 2001 From: scottinet Date: Tue, 17 Nov 2015 15:47:06 +0100 Subject: [PATCH 2/4] added offline mode arguments --- src/kuzzle.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/kuzzle.js b/src/kuzzle.js index 82fabbd66..6add22ea2 100644 --- a/src/kuzzle.js +++ b/src/kuzzle.js @@ -109,6 +109,35 @@ module.exports = Kuzzle = function (url, options, cb) { value: {}, enumerable: true, writable: true + }, + /* + Offline queue use the following format: + [ + timestamp: { + query: 'query', + cb: callbackFunction + } + ] + */ + offlineQueue: { + value: [], + enumerable: true, + writable: true + }, + queueFilter: { + value: null, + enumerable: true, + writable: true + }, + queueMaxSize: { + value: 500, + enumerable: true, + writable: true + }, + queueTTL: { + value: 120000, + enumerable: true, + writable: true } }); From 9f165a22b9012264a32ad267f3468f2ee8d2fa8e Mon Sep 17 00:00:00 2001 From: scottinet Date: Wed, 18 Nov 2015 10:07:53 +0100 Subject: [PATCH 3/4] implemented offline mode --- package.json | 2 +- src/kuzzle.js | 265 +++++++++++++++++++++++++++++++----- src/kuzzleDataCollection.js | 61 +++++++-- src/kuzzleDataMapping.js | 20 ++- src/kuzzleDocument.js | 20 ++- src/kuzzleRoom.js | 34 ++--- 6 files changed, 323 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index e693026a0..e12fa52c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kuzzle-sdk", - "version": "1.0.0-alpha.3", + "version": "1.0.0-alpha.4", "description": "A connector for the Kuzzle API", "author": "The Kuzzle Team ", "repository": { diff --git a/src/kuzzle.js b/src/kuzzle.js index 62ff63300..87db3a4b9 100644 --- a/src/kuzzle.js +++ b/src/kuzzle.js @@ -50,6 +50,10 @@ module.exports = Kuzzle = function (url, options, cb) { reconnected: [] } }, + queuing: { + value: false, + writable: true + }, requestHistory: { value: {}, writable: true @@ -66,9 +70,14 @@ module.exports = Kuzzle = function (url, options, cb) { /* Contains the centralized subscription list in the following format: pending: - 'roomId': [ kuzzleRoomID_1, kuzzleRoomID_2, kuzzleRoomID_... ] + 'roomId': { + kuzzleRoomID_1: kuzzleRoomInstance_1, + kuzzleRoomID_2: kuzzleRoomInstance_2, + kuzzleRoomID_...: kuzzleRoomInstance_... + } This was made to allow multiple subscriptions on the same set of filters, something that Kuzzle does not permit. + This structure also allows renewing subscriptions after a connection loss */ value: { pending: 0 @@ -113,7 +122,8 @@ module.exports = Kuzzle = function (url, options, cb) { /* Offline queue use the following format: [ - timestamp: { + { + ts: , query: 'query', cb: callbackFunction } @@ -138,6 +148,11 @@ module.exports = Kuzzle = function (url, options, cb) { value: 120000, enumerable: true, writable: true + }, + replayInterval: { + value: 10, + enumerable: true, + writable: true } }); @@ -210,9 +225,9 @@ function construct(url, cb) { throw new Error('URL to Kuzzle can\'t be empty'); } - this.socket = io(url, {reconnection: this.autoReconnect, reconnectionDelay: this.reconnectionDelay}); + self.socket = io(url, {reconnection: this.autoReconnect, reconnectionDelay: this.reconnectionDelay}); - this.socket.once('connect', function () { + self.socket.once('connect', function () { self.state = 'connected'; if (cb) { @@ -220,9 +235,8 @@ function construct(url, cb) { } }); - this.socket.once('error', function (error) { + self.socket.once('error', function (error) { self.state = 'error'; - self.logout(); if (cb) { @@ -230,29 +244,129 @@ function construct(url, cb) { } }); - this.socket.on('disconnect', function () { + self.socket.on('disconnect', function () { self.state = 'offline'; if (!self.autoReconnect) { self.logout(); } + if (self.autoQueue) { + self.queuing = true; + } + self.eventListeners.disconnected.forEach(function (listener) { listener(); }); }); - this.socket.on('reconnect', function () { - self.state = 'connected'; + self.socket.on('reconnect', function () { + self.state = 'reconnecting'; + + // renew subscriptions + if (self.autoResubscribe) { + Object.keys(self.subscriptions).forEach(function (roomId) { + Object.keys(self.subscriptions[roomId]).forEach(function (subscriptionId) { + var subscription = self.subscriptions[roomId][subscriptionId]; + + subscription.renew(subscription.callback); + }); + }); + } + + // replay queued requests + if (self.autoReplay) { + cleanQueue.call(this); + dequeue.call(this); + } + // alert listeners self.eventListeners.reconnected.forEach(function (listener) { listener(); }); + + self.state = 'connected'; }); return this; } +/** + * Clean up the queue, ensuring the queryTTL and queryMaxSize properties are respected + */ +function cleanQueue () { + var + self = this, + now = Date.now(), + lastDocumentIndex = -1; + + if (self.queueTTL > 0) { + self.offlineQueue.forEach(function (query, index) { + if (query.ts < now - self.queueTTL) { + lastDocumentIndex = index; + } + }); + + if (lastDocumentIndex !== -1) { + self.offlineQueue.splice(0, lastDocumentIndex + 1); + } + } + + if (self.queueMaxSize > 0) { + if (self.offlineQueue.length >= self.queueMaxSize) { + self.offlineQueue.splice(0, self.offlineQueue.length - self.queueMaxSize); + } + } +} + +/** + * Emit a request to Kuzzle + * + * @param {object} request + * @param {responseCallback} [cb] + */ +function emitRequest (request, cb) { + var + now = Date.now(), + self = this; + + if (cb) { + self.socket.once(request.requestId, function (response) { + cb(response.error, response.result); + }); + } + + self.socket.emit('kuzzle', request); + + // Track requests made to allow KuzzleRoom.subscribeToSelf to work + self.requestHistory[request.requestId] = now; + + // Clean history from requests made more than 10s ago + Object.keys(self.requestHistory).forEach(function (key) { + if (self.requestHistory[key] < now - 10000) { + delete self.requestHistory[key]; + } + }); +} + +/** + * Play all queued requests, in order. + */ +function dequeue () { + var self = this; + + if (self.offlineQueue.length > 0) { + emitRequest.call(self, self.offlineQueue[0].query, self.offlineQueue[0].cb); + self.offlineQueue.shift(); + + setTimeout(function () { + dequeue(); + }, Math.max(0, self.replayInterval)); + } else { + self.queuing = false; + } +} + /** * Adds a listener to a Kuzzle global event. When an event is fired, listeners are called in the order of their * insertion. @@ -290,14 +404,21 @@ Kuzzle.prototype.addListener = function(event, listener) { * Kuzzle monitors active connections, and ongoing/completed/failed requests. * This method returns all available statistics from Kuzzle. * + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {object} this */ -Kuzzle.prototype.getAllStatistics = function (cb) { +Kuzzle.prototype.getAllStatistics = function (options, cb) { this.isValid(); + + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + this.callbackRequired('Kuzzle.getAllStatistics', cb); - this.query(null, 'admin', 'getAllStats', {}, function (err, res) { + this.query(null, 'admin', 'getAllStats', {}, options, function (err, res) { var result = []; if (err) { @@ -322,21 +443,34 @@ Kuzzle.prototype.getAllStatistics = function (cb) { * This method allows getting either the last statistics frame, or a set of frames starting from a provided timestamp. * * @param {number} timestamp - Epoch time. Starting time from which the frames are to be retrieved + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {object} this */ -Kuzzle.prototype.getStatistics = function (timestamp, cb) { - if (!cb && typeof timestamp === 'function') { - cb = timestamp; - timestamp = null; +Kuzzle.prototype.getStatistics = function (timestamp, options, cb) { + if (!cb) { + if (arguments.length === 1) { + cb = arguments[0]; + options = null; + timestamp = null; + } else { + cb = arguments[1]; + if (arguments[0] === 'object') { + options = arguments[0]; + timestamp = null; + } else { + timestamp = arguments[0]; + options = null; + } + } } this.isValid(); this.callbackRequired('Kuzzle.getStatistics', cb); if (!timestamp) { - this.query(null, 'admin', 'getStats', {}, function (err, res) { - var frame = {}; + this.query(null, 'admin', 'getStats', {}, options, function (err, res) { + var frame; if (err) { return cb(err); @@ -392,14 +526,31 @@ Kuzzle.prototype.dataCollectionFactory = function(collection, headers) { return this.collections[collection]; }; +/** + * Empties the offline queue without replaying it. + * + * @returns {Kuzzle} + */ +Kuzzle.prototype.flushQueue = function () { + this.offlineQueue = []; + return this; +}; + /** * Returns the list of known persisted data collections. * + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {object} this */ -Kuzzle.prototype.listCollections = function (cb) { +Kuzzle.prototype.listCollections = function (options, cb) { this.isValid(); + + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + this.callbackRequired('Kuzzle.listCollections', cb); this.query(null, 'read', 'listCollections', {}, function (err, res) { @@ -432,12 +583,18 @@ Kuzzle.prototype.logout = function () { /** * Return the current Kuzzle's UTC Epoch time, in milliseconds - * + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {object} this */ -Kuzzle.prototype.now = function (cb) { +Kuzzle.prototype.now = function (options, cb) { this.isValid(); + + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + this.callbackRequired('Kuzzle.now', cb); this.query(null, 'read', 'now', {}, function (err, res) { @@ -470,7 +627,6 @@ Kuzzle.prototype.now = function (cb) { Kuzzle.prototype.query = function (collection, controller, action, query, options, cb) { var attr, - now = Date.now(), object = { action: action, controller: controller, @@ -480,7 +636,7 @@ Kuzzle.prototype.query = function (collection, controller, action, query, option this.isValid(); - if (!cb && options && typeof options === 'function') { + if (!cb && typeof options === 'function') { cb = options; options = null; } @@ -491,6 +647,10 @@ Kuzzle.prototype.query = function (collection, controller, action, query, option object.metadata[meta] = options.metadata[meta]; }); } + + if (options.queuable === false && self.state === 'offline') { + return self; + } } if (query.metadata) { @@ -515,23 +675,19 @@ Kuzzle.prototype.query = function (collection, controller, action, query, option object.requestId = uuid.v4(); } - if (cb) { - self.socket.once(object.requestId, function (response) { - cb(response.error, response.result); - }); - } - - self.socket.emit('kuzzle', object); - - // Track requests made to allow KuzzleRoom.subscribeToSelf to work - self.requestHistory[object.requestId] = now; + if (self.state === 'connected' || (options && options.queuable === false)) { + emitRequest.call(this, object, cb); + } else if (self.queuing) { + cleanQueue.call(this, object, cb); - // Clean history from requests made more than 30s ago - Object.keys(self.requestHistory).forEach(function (key) { - if (self.requestHistory[key] < now - 30000) { - delete self.requestHistory[key]; + if (self.queueFilter) { + if (self.queueFilter(query)) { + self.offlineQueue.push({ts: Date.now(), query: query, cb: cb}); + } + } else { + self.offlineQueue.push({ts: Date.now(), query: query, cb: cb}); } - }); + } return self; }; @@ -585,6 +741,19 @@ Kuzzle.prototype.removeListener = function (event, listenerId) { }); }; +/** + * Replays the requests queued during offline mode. + * Works only if the SDK is not in a disconnected state, and if the autoReplay option is set to false. + */ +Kuzzle.prototype.replayQueue = function () { + if (this.state !== 'offline' && !this.autoReplay) { + cleanQueue.call(this); + dequeue.call(this); + } + + return this; +}; + /** * Helper function allowing to set headers while chaining calls. * @@ -611,3 +780,25 @@ Kuzzle.prototype.setHeaders = function(content, replace) { return self; }; + +/** + * Starts the requests queuing. Works only during offline mode, and if the autoQueue option is set to false. + */ +Kuzzle.prototype.startQueuing = function () { + if (self.state === 'offline' && !self.autoQueue) { + self.queuing = true; + } + + return this; +}; + +/** + * Stops the requests queuing. Works only during offline mode, and if the autoQueue option is set to false. + */ +Kuzzle.prototype.stopQueuing = function () { + if (self.state === 'offline' && !self.autoQueue) { + self.queuing = false; + } + + return this; +}; diff --git a/src/kuzzleDataCollection.js b/src/kuzzleDataCollection.js index 58883f437..809d353f1 100644 --- a/src/kuzzleDataCollection.js +++ b/src/kuzzleDataCollection.js @@ -52,19 +52,25 @@ function KuzzleDataCollection(kuzzle, collection) { * That means that a document that was just been created won’t be returned by this function. * * @param {object} filters - Filters in Elasticsearch Query DSL format + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {Object} this */ -KuzzleDataCollection.prototype.advancedSearch = function (filters, cb) { +KuzzleDataCollection.prototype.advancedSearch = function (filters, options, cb) { var query, self = this; + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + self.kuzzle.callbackRequired('KuzzleDataCollection.advancedSearch', cb); query = self.kuzzle.addHeaders({body: filters}, this.headers); - self.kuzzle.query(this.collection, 'read', 'search', query, function (error, result) { + self.kuzzle.query(this.collection, 'read', 'search', query, options, function (error, result) { var documents = []; if (error) { @@ -89,17 +95,23 @@ KuzzleDataCollection.prototype.advancedSearch = function (filters, cb) { * That means that a document that was just been created won’t be returned by this function * * @param {object} filters - Filters in Elasticsearch Query DSL format + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {Object} this */ -KuzzleDataCollection.prototype.count = function (filters, cb) { +KuzzleDataCollection.prototype.count = function (filters, options, cb) { var query; + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + this.kuzzle.callbackRequired('KuzzleDataCollection.count', cb); query = this.kuzzle.addHeaders({body: filters}, this.headers); - this.kuzzle.query(this.collection, 'read', 'count', query, function (error, result) { + this.kuzzle.query(this.collection, 'read', 'count', query, options, function (error, result) { if (error) { return cb(error); } @@ -131,7 +143,7 @@ KuzzleDataCollection.prototype.createDocument = function (document, options, cb) data = {}, action = 'create'; - if (!cb && options && typeof options === 'function') { + if (!cb && typeof options === 'function') { cb = options; options = null; } @@ -193,7 +205,7 @@ KuzzleDataCollection.prototype.deleteDocument = function (arg, options, cb) { action = 'deleteByQuery'; } - if (options && !cb && typeof options === 'function') { + if (!cb && typeof options === 'function') { cb = options; options = null; } @@ -223,18 +235,24 @@ KuzzleDataCollection.prototype.deleteDocument = function (arg, options, cb) { * Retrieve a single stored document using its unique document ID. * * @param {string} documentId - Unique document identifier + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {Object} this */ -KuzzleDataCollection.prototype.fetchDocument = function (documentId, cb) { +KuzzleDataCollection.prototype.fetchDocument = function (documentId, options, cb) { var data = {_id: documentId}, self = this; + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + self.kuzzle.callbackRequired('KuzzleDataCollection.fetch', cb); data = self.kuzzle.addHeaders(data, this.headers); - self.kuzzle.query(this.collection, 'read', 'get', data, function (err, res) { + self.kuzzle.query(this.collection, 'read', 'get', data, options, function (err, res) { if (err) { return cb(err); } @@ -248,13 +266,19 @@ KuzzleDataCollection.prototype.fetchDocument = function (documentId, cb) { /** * Retrieves all documents stored in this data collection * + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Handles the query response * @returns {Object} this */ -KuzzleDataCollection.prototype.fetchAllDocuments = function (cb) { +KuzzleDataCollection.prototype.fetchAllDocuments = function (options, cb) { + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + this.kuzzle.callbackRequired('KuzzleDataCollection.fetchAll', cb); - this.advancedSearch({}, cb); + this.advancedSearch({}, options, cb); return this; }; @@ -263,16 +287,22 @@ KuzzleDataCollection.prototype.fetchAllDocuments = function (cb) { /** * Instantiates a KuzzleDataMapping object containing the current mapping of this collection. * + * @param {object} [options] - Optional parameters * @param {responseCallback} cb - Returns an instantiated KuzzleDataMapping object * @return {object} this */ -KuzzleDataCollection.prototype.getMapping = function (cb) { +KuzzleDataCollection.prototype.getMapping = function (options, cb) { var kuzzleMapping; + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + this.kuzzle.callbackRequired('KuzzleDataCollection.getMapping', cb); kuzzleMapping = new KuzzleDataMapping(this); - kuzzleMapping.refresh(cb); + kuzzleMapping.refresh(options, cb); return this; }; @@ -325,7 +355,7 @@ KuzzleDataCollection.prototype.replaceDocument = function (documentId, content, body: content }; - if (options && !cb && typeof options === 'function') { + if (!cb && typeof options === 'function') { cb = options; options = null; } @@ -388,6 +418,11 @@ KuzzleDataCollection.prototype.updateDocument = function (documentId, content, o }, self = this; + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + data = self.kuzzle.addHeaders(data, this.headers); if (cb) { diff --git a/src/kuzzleDataMapping.js b/src/kuzzleDataMapping.js index 144a43fee..894f21398 100644 --- a/src/kuzzleDataMapping.js +++ b/src/kuzzleDataMapping.js @@ -52,14 +52,20 @@ function KuzzleDataMapping(kuzzleDataCollection) { /** * Applies the new mapping to the data collection. * + * @param {object} [options] - Optional parameters * @param {responseCallback} [cb] - Handles the query response */ -KuzzleDataMapping.prototype.apply = function (cb) { +KuzzleDataMapping.prototype.apply = function (options, cb) { var self = this, data = this.kuzzle.addHeaders({body: {properties: this.mapping}}, this.headers); - self.kuzzle.query(this.collection, 'admin', 'putMapping', data, function (err, res) { + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + + self.kuzzle.query(this.collection, 'admin', 'putMapping', data, options, function (err, res) { if (err) { return cb ? cb(err) : false; } @@ -79,15 +85,21 @@ KuzzleDataMapping.prototype.apply = function (cb) { * * Calling this function will discard any uncommited changes. You can commit changes by calling the “apply” function * + * @param {object} [options] - Optional parameters * @param {responseCallback} [cb] - Handles the query response * @returns {*} this */ -KuzzleDataMapping.prototype.refresh = function (cb) { +KuzzleDataMapping.prototype.refresh = function (options, cb) { var self = this, data = this.kuzzle.addHeaders({}, this.headers); - this.kuzzle.query(this.collection, 'admin', 'getMapping', data, function (err, res) { + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + + this.kuzzle.query(this.collection, 'admin', 'getMapping', data, options, function (err, res) { if (err) { return cb ? cb(err) : false; } diff --git a/src/kuzzleDocument.js b/src/kuzzleDocument.js index bd612c85b..175b6c52e 100644 --- a/src/kuzzleDocument.js +++ b/src/kuzzleDocument.js @@ -143,13 +143,13 @@ KuzzleDocument.prototype.toString = function () { KuzzleDocument.prototype.delete = function (options, cb) { var self = this; - if (options && !cb && typeof options === 'function') { + if (!cb && typeof options === 'function') { cb = options; options = null; } if (this.refreshing) { - this.queue.push({action: 'delete', args: [cb]}); + this.queue.push({action: 'delete', args: [options, cb]}); return this; } @@ -175,14 +175,20 @@ KuzzleDocument.prototype.delete = function (options, cb) { /** * Replaces the current content with the last version of this document stored in Kuzzle. * + * @param {object} [options] - Optional parameters * @param {responseCallback} [cb] - Handles the query response * @returns {*} this */ -KuzzleDocument.prototype.refresh = function (cb) { +KuzzleDocument.prototype.refresh = function (options, cb) { var self = this; + if (!cb && typeof options === 'function') { + cb = options; + options = null; + } + if (this.refreshing) { - this.queue.push({action: 'refresh', args: [cb]}); + this.queue.push({action: 'refresh', args: [options, cb]}); return this; } @@ -192,7 +198,7 @@ KuzzleDocument.prototype.refresh = function (cb) { self.refreshing = true; - self.kuzzle.query(self.collection, 'read', 'get', {_id: self.id}, function (error, result) { + self.kuzzle.query(self.collection, 'read', 'get', {_id: self.id}, options, function (error, result) { if (error) { self.refreshing = false; self.queue = []; @@ -250,7 +256,7 @@ KuzzleDocument.prototype.save = function (options, cb) { }; if (self.refreshing) { - self.queue.push({action: 'save', args: [replace, cb]}); + self.queue.push({action: 'save', args: [options, cb]}); return self; } @@ -275,7 +281,7 @@ KuzzleDocument.prototype.publish = function (options) { var data = this.toJSON(); if (this.refreshing) { - this.queue.push({action: 'publish', args: []}); + this.queue.push({action: 'publish', args: [options]}); return this; } diff --git a/src/kuzzleRoom.js b/src/kuzzleRoom.js index 704dce2e5..c2d5000ad 100644 --- a/src/kuzzleRoom.js +++ b/src/kuzzleRoom.js @@ -181,12 +181,12 @@ KuzzleRoom.prototype.renew = function (filters, cb) { self.roomId = response.roomId; } - if (self.kuzzle.subscriptions[self.roomId]) { - self.kuzzle.subscriptions[self.roomId].push(self.id); - } else { - self.kuzzle.subscriptions[self.roomId] = [self.id]; + if (!self.kuzzle.subscriptions[self.roomId]) { + self.kuzzle.subscriptions[self.roomId] = {}; } + self.kuzzle.subscriptions[self.roomId][self.id] = self; + self.notifier = notificationCallback.bind(self); self.kuzzle.socket.on(self.roomId, self.notifier); @@ -210,22 +210,22 @@ KuzzleRoom.prototype.renew = function (filters, cb) { KuzzleRoom.prototype.unsubscribe = function () { var self = this, - room = this.roomId, + room = self.roomId, interval; - if (this.subscribing) { - this.queue.push({action: 'unsubscribe', args: []}); - return this; + if (self.subscribing) { + self.queue.push({action: 'unsubscribe', args: []}); + return self; } - if (this.roomId) { - this.kuzzle.socket.off(this.roomId, this.notifier); + if (room) { + self.kuzzle.socket.off(room, this.notifier); - if (this.kuzzle.subscriptions[this.roomId].length === 1) { - delete this.kuzzle.subscriptions[this.roomId]; + if (Object.keys(self.kuzzle.subscriptions[room]).length === 1) { + delete self.kuzzle.subscriptions[room]; - if (this.kuzzle.subscriptions.pending === 0) { - this.kuzzle.query(this.collection, 'subscribe', 'off', {body: {roomId: this.roomId}}); + if (self.kuzzle.subscriptions.pending === 0) { + self.kuzzle.query(this.collection, 'subscribe', 'off', {body: {roomId: room}}); } else { interval = setInterval(function () { if (self.kuzzle.subscriptions.pending === 0) { @@ -237,13 +237,13 @@ KuzzleRoom.prototype.unsubscribe = function () { }, 500); } } else { - this.kuzzle.subscriptions[this.roomId].splice(this.kuzzle.subscriptions[this.roomId].indexOf(this.id), 1); + delete self.kuzzle.subscriptions[room][self.id]; } - this.roomId = null; + self.roomId = null; } - return this; + return self; }; /** From a57af64407559ae88675f2c8bd1a81bc77bfc2ec Mon Sep 17 00:00:00 2001 From: scottinet Date: Wed, 18 Nov 2015 10:23:16 +0100 Subject: [PATCH 4/4] bugfixes --- src/kuzzle.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/kuzzle.js b/src/kuzzle.js index 87db3a4b9..da2a47d9d 100644 --- a/src/kuzzle.js +++ b/src/kuzzle.js @@ -312,10 +312,8 @@ function cleanQueue () { } } - if (self.queueMaxSize > 0) { - if (self.offlineQueue.length >= self.queueMaxSize) { - self.offlineQueue.splice(0, self.offlineQueue.length - self.queueMaxSize); - } + if (self.queueMaxSize > 0 && self.offlineQueue.length > self.queueMaxSize) { + self.offlineQueue.splice(0, self.offlineQueue.length - self.queueMaxSize); } } @@ -455,7 +453,7 @@ Kuzzle.prototype.getStatistics = function (timestamp, options, cb) { timestamp = null; } else { cb = arguments[1]; - if (arguments[0] === 'object') { + if (typeof arguments[0] === 'object') { options = arguments[0]; timestamp = null; } else { @@ -553,7 +551,7 @@ Kuzzle.prototype.listCollections = function (options, cb) { this.callbackRequired('Kuzzle.listCollections', cb); - this.query(null, 'read', 'listCollections', {}, function (err, res) { + this.query(null, 'read', 'listCollections', {}, options, function (err, res) { if (err) { return cb(err); } @@ -785,8 +783,8 @@ Kuzzle.prototype.setHeaders = function(content, replace) { * Starts the requests queuing. Works only during offline mode, and if the autoQueue option is set to false. */ Kuzzle.prototype.startQueuing = function () { - if (self.state === 'offline' && !self.autoQueue) { - self.queuing = true; + if (this.state === 'offline' && !this.autoQueue) { + this.queuing = true; } return this; @@ -796,8 +794,8 @@ Kuzzle.prototype.startQueuing = function () { * Stops the requests queuing. Works only during offline mode, and if the autoQueue option is set to false. */ Kuzzle.prototype.stopQueuing = function () { - if (self.state === 'offline' && !self.autoQueue) { - self.queuing = false; + if (this.state === 'offline' && !this.autoQueue) { + this.queuing = false; } return this;