diff --git a/bower.json b/bower.json index 04ab998..317be54 100644 --- a/bower.json +++ b/bower.json @@ -1,8 +1,9 @@ { "name": "angular-sails", - "version": "1.1.0", + "version": "2.0.0-beta.2", "authors": [ - "Jan-Oliver Pantel " + "Jan-Oliver Pantel ", + "Evan Sharp " ], "description": "An angular provider for using the sails socket.io api", "main": "./dist/angular-sails.js", @@ -16,7 +17,7 @@ "api" ], "license": "MIT", - "homepage": "https://github.com/kyjan/angular-sails", + "homepage": "https://github.com/janpantel/angular-sails", "dependencies": { "angular": ">=1.2.*", "socket.io-client": ">=1.3.0" diff --git a/mock/socket-io.js b/mock/socket-io.js index a27ab65..5715fd2 100644 --- a/mock/socket-io.js +++ b/mock/socket-io.js @@ -60,6 +60,16 @@ function createMockSocketObject() { delete this._listeners[ev]; } }, + off: function(ev, fn) { + if (fn) { + var index = this._listeners[ev].indexOf(fn); + if (index > -1) { + this._listeners[ev].splice(index, 1); + } + } else { + delete this._listeners[ev]; + } + }, removeAllListeners: function(ev) { if (ev) { delete this._listeners[ev]; diff --git a/package.json b/package.json index ca2c918..1c17414 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,27 @@ { "name": "angular-sails", "private": true, - "version": "1.1.0", + "version": "2.0.0-beta.2", "description": "An angular provider for using the sails socket.io api", "scripts": { - "build": "gulp build-js", + "build": "gulp build", "test": "gulp test" }, + "main": "dist/angular-sails.js", "repository": { "type": "git", "url": "https://github.com/kyjan/angular-sails.git" }, - "author": "Jan-Oliver Pantel ", + "author": "Jan-Oliver Pantel ", + "contributors": [ + "Jan-Oliver Pantel ", + "Evan Sharp " + ], "license": "MIT", "bugs": { - "url": "https://github.com/kyjan/angular-sails/issues" + "url": "https://github.com/janpantel/angular-sails/issues" }, - "homepage": "https://github.com/kyjan/angular-sails", + "homepage": "https://github.com/janpantel/angular-sails", "devDependencies": { "chai": "^1.10.0", "gulp": "^3.5.6", diff --git a/src/service/$sails.js b/src/service/$sails.js index 5dc646e..0f3578e 100644 --- a/src/service/$sails.js +++ b/src/service/$sails.js @@ -2,6 +2,7 @@ /* jshint newcap: false */ /* global headersGetter: true, + mergeHeaders: true, arrIndexOf: true, isFile: true, isBlob: true @@ -26,9 +27,9 @@ function $sails($sailsInterceptorProvider, $sailsIoProvider) { }; provider.defaults = { - transformResponse: [], - transformRequest: [], - headers: {} // TODO: default headers + transformResponse: [], + transformRequest: [], + headers: {} // TODO: what should the default default headers }; provider.interceptors = $sailsInterceptorProvider.interceptors = [ @@ -42,9 +43,9 @@ function $sails($sailsInterceptorProvider, $sailsIoProvider) { }*/ ]; - this.$get = function($q, $log, $timeout, $sailsIo, $sailsInterceptor) { + this.$get = function($rootScope, $q, $log, $timeout, $sailsIo, $sailsInterceptor) { var socket = new $sailsIo(provider.socket || provider.url, provider.config); - var socketFunctions = ['connect','disconnect','isConnected']; + var socketFunctions = ['connect', 'disconnect', 'isConnected']; function $sails(config) { return $sails[config.method](config.url, config); @@ -52,7 +53,7 @@ function $sails($sailsInterceptorProvider, $sailsIoProvider) { $sails._socket = socket; - function exposeSocketFunction(fnName){ + function exposeSocketFunction(fnName) { $sails[fnName] = socket[fnName].bind(socket); } @@ -62,8 +63,7 @@ function $sails($sailsInterceptorProvider, $sailsIoProvider) { var config = { method: methodName, transformRequest: provider.defaults.transformRequest, - transformResponse: provider.defaults.transformResponse, - headers: {}// TODO: default headers + transformResponse: provider.defaults.transformResponse }; requestConfig = requestConfig || {}; @@ -76,9 +76,14 @@ function $sails($sailsInterceptorProvider, $sailsIoProvider) { requestConfig.data = data; } - angular.extend(config, requestConfig); + config = angular.extend({}, config, requestConfig); + config.headers = mergeHeaders(requestConfig, provider.defaults.headers); config.url = (provider.urlPrefix || '') + (url || config.url); - config.method = (config.method || methodName).toUpperCase(); + config.method = angular.uppercase(config.method || methodName); + + if (angular.isUndefined(config.withCredentials) && !angular.isUndefined(provider.defaults.withCredentials)) { + config.withCredentials = provider.defaults.withCredentials; + } var promise = $sailsInterceptor(socket[methodName].bind(socket), config); @@ -113,56 +118,62 @@ function $sails($sailsInterceptorProvider, $sailsIoProvider) { * Update a model on sails pushes * @param {String} name Sails model name * @param {Array} models Array with model objects + * @returns {Function} Function to remove the model updater instance */ $sails.$modelUpdater = function(name, models) { - socket.on(name, function(message) { - var i; + var update = function(message) { - if (provider.debug) { - $log.info('$sails ' + name + ' model ' + message.verb + ' id: ' + message.id, message.data); - } + $rootScope.$evalAsync(function() { + var i; - switch (message.verb) { + switch (message.verb) { - case "created": - // create new model item - models.push(message.data); - break; + case "created": + // create new model item + models.push(message.data); + break; - case "updated": - var obj; - for (i = 0; i < models.length; i++) { - if (models[i].id === message.id) { - obj = models[i]; - break; + case "updated": + var obj; + for (i = 0; i < models.length; i++) { + if (models[i].id === message.id) { + obj = models[i]; + break; + } } - } - - // cant update if the angular-model does not have the item and the - // sails message does not give us the previous record - if (!obj && !message.previous) return; - - if (!obj) { - // sails has given us the previous record, create it in our model - obj = message.previous; - models.push(obj); - } - - // update the model item - angular.extend(obj, message.data); - break; - - case "destroyed": - for (i = 0; i < models.length; i++) { - if (models[i].id === message.id) { - models.splice(i, 1); - break; + + // cant update if the angular-model does not have the item and the + // sails message does not give us the previous record + if (!obj && !message.previous) return; + + if (!obj) { + // sails has given us the previous record, create it in our model + obj = message.previous; + models.push(obj); } - } - break; - } - }); + + // update the model item + angular.extend(obj, message.data); + break; + + case "destroyed": + for (i = 0; i < models.length; i++) { + if (models[i].id === message.id) { + models.splice(i, 1); + break; + } + } + break; + } + }); + }; + + socket._socket.on(name, update); + + return function() { + socket._socket.off(name, update); + }; }; $sails.defaults = this.defaults; diff --git a/src/service/$sailsIo.js b/src/service/$sailsIo.js index cc0a059..a1fe607 100644 --- a/src/service/$sailsIo.js +++ b/src/service/$sailsIo.js @@ -85,7 +85,7 @@ function $sailsIo() { SailsIo.prototype._send = function(req, res) { var self = this; - var sailsEndpoint = req.method.toLowerCase(); + var sailsEndpoint = angular.lowercase(req.method); self.connectDefer.promise.then(function sendRequest() { if (provider.debug) { @@ -132,7 +132,7 @@ function $sailsIo() { req.url = buildUrl(req.url.replace(/^(.+)\/*\s*$/, '$1'), req.params); req.headers = req.headers || {}; req.data = req.data || {}; - req.method = req.method.toUpperCase(); + req.method = angular.uppercase(req.method); if (typeof req.url !== 'string') { throw new Error('Invalid or missing URL!'); diff --git a/src/util.js b/src/util.js index 210cb02..fddc814 100644 --- a/src/util.js +++ b/src/util.js @@ -16,6 +16,67 @@ function parseHeaders(headers) { return parsed; } +function executeHeaderFns(headers, config) { + var headerContent, processedHeaders = {}; + + angular.forEach(headers, function(headerFn, header) { + if (angular.isFunction(headerFn)) { + headerContent = headerFn(config); + if (headerContent != null) { + processedHeaders[header] = headerContent; + } + } else { + processedHeaders[header] = headerFn; + } + }); + + return processedHeaders; +} + +function mergeHeaders(config, defHeaders) { + var reqHeaders = angular.extend({}, config.headers), + defHeaderName, lowercaseDefHeaderName, reqHeaderName; + + defHeaders = angular.extend({}, defHeaders.common, defHeaders[angular.lowercase(config.method)]); + + // using for-in instead of forEach to avoid unecessary iteration after header has been found + defaultHeadersIteration: + for (defHeaderName in defHeaders) { + lowercaseDefHeaderName = angular.lowercase(defHeaderName); + + for (reqHeaderName in reqHeaders) { + if (angular.lowercase(reqHeaderName) === lowercaseDefHeaderName) { + continue defaultHeadersIteration; + } + } + + reqHeaders[defHeaderName] = defHeaders[defHeaderName]; + } + + // execute if header value is a function for merged headers + return executeHeaderFns(reqHeaders, shallowCopy(config)); +} + +function shallowCopy(src, dst) { + if (angular.isArray(src)) { + dst = dst || []; + + for (var i = 0, ii = src.length; i < ii; i++) { + dst[i] = src[i]; + } + } else if (angular.isObject(src)) { + dst = dst || {}; + + for (var key in src) { + if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + } + + return dst || src; +} + function trim(value) { return angular.isString(value) ? value.trim() : value; } diff --git a/test/angular-sails-service.spec.js b/test/angular-sails-service.spec.js index 544b4fc..068b6bf 100644 --- a/test/angular-sails-service.spec.js +++ b/test/angular-sails-service.spec.js @@ -232,10 +232,11 @@ describe('Agnular Sails service', function() { var models; var modelResponse; + var removeUpdater; beforeEach(function() { models = []; - $sails.$modelUpdater('user', models); + removeUpdater = $sails.$modelUpdater('user', models); modelResponse = { created:{ data: { @@ -291,6 +292,28 @@ describe('Agnular Sails service', function() { expect(models[0].name).to.equal(modelResponse.updated.data.name); }); + describe('returned function', function(){ + it('should remove the socket listener', function () { + removeUpdater(); + $scope.$digest(); + mockIoSocket.emit('user', modelResponse.created); + $scope.$digest(); + expect(models).to.be.empty(); + }); + + it('should remove only the socket listener it is called on', function () { + var tasks = []; + $sails.$modelUpdater('tasks', tasks); + removeUpdater(); + $scope.$digest(); + mockIoSocket.emit('user', modelResponse.created); + mockIoSocket.emit('tasks', modelResponse.created); + $scope.$digest(); + expect(tasks).to.contain(modelResponse.created.data); + expect(models).to.be.empty(); + }); + }); + }); });