From 9f2d1231674dd5c92acac7f4102d2457721672e6 Mon Sep 17 00:00:00 2001 From: Jan Pantel Date: Fri, 12 Dec 2014 13:34:48 +0100 Subject: [PATCH 1/6] chore(version): 1.0.5 --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 3bd13d2..0e27f76 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-sails", - "version": "1.0.4", + "version": "1.0.5", "authors": [ "Jan-Oliver Pantel " ], From ab868baad66ebd3f6fb25d283e8edefc39897ae3 Mon Sep 17 00:00:00 2001 From: Christoph Wiechert Date: Sat, 17 Jan 2015 12:56:23 +0100 Subject: [PATCH 2/6] Refactoring --- src/service/angular-sails.js | 236 ++++++++++++++++++++++++----------- 1 file changed, 165 insertions(+), 71 deletions(-) diff --git a/src/service/angular-sails.js b/src/service/angular-sails.js index 0387551..c49929c 100644 --- a/src/service/angular-sails.js +++ b/src/service/angular-sails.js @@ -1,74 +1,168 @@ -/*jslint sloppy:true*/ -/*global angular, io */ -angular.module('ngSails').provider('$sails', function () { - var provider = this, - httpVerbs = ['get', 'post', 'put', 'delete'], - eventNames = ['on', 'once']; +(function(angular, io) { + 'use strict'; + io.sails.autoConnect = false; + window.io.sails.useCORSRouteToGetCookie = false; + + /*global angular */ + angular.module('ngSails', ['ng']); + + /*global angular, io */ + angular.module('ngSails').provider('$sails', function() { + var provider = this; + + // wrap these socket.io methods with angular promises + this.httpVerbs = ['get', 'post', 'put', 'delete']; + + // wrap these events in $evalAsync to fire a digest cycle + this.eventNames = ['on', 'once']; this.url = undefined; - this.interceptors = []; - this.responseHandler = undefined; - - this.$get = ['$q', '$timeout', function ($q, $timeout) { - var socket = io.connect(provider.url), - defer = function () { - var deferred = $q.defer(), - promise = deferred.promise; - - promise.success = function (fn) { - promise.then(fn); - return promise; - }; - - promise.error = function (fn) { - promise.then(null, fn); - return promise; - }; - - return deferred; - }, - resolveOrReject = this.responseHandler || function (deferred, data) { - // Make sure what is passed is an object that has a status that is a number and if that status is no 2xx, reject. - if (data && angular.isObject(data) && data.status && !isNaN(data.status) && Math.floor(data.status / 100) !== 2) { - deferred.reject(data); - } else { - deferred.resolve(data); - } - }, - angularify = function (cb, data) { - $timeout(function () { - cb(data); - }); - }, - promisify = function (methodName) { - socket['legacy_' + methodName] = socket[methodName]; - socket[methodName] = function (url, data, cb) { - var deferred = defer(); - if (cb === undefined && angular.isFunction(data)) { - cb = data; - data = null; - } - deferred.promise.then(cb); - socket['legacy_' + methodName](url, data, function (result) { - resolveOrReject(deferred, result); - }); - return deferred.promise; - }; - }, - wrapEvent = function (eventName) { - socket['legacy_' + eventName] = socket[eventName]; - socket[eventName] = function (event, cb) { - if (cb !== null && angular.isFunction(cb)) { - socket['legacy_' + eventName](event, function (result) { - angularify(cb, result); - }); - } - }; - }; - - angular.forEach(httpVerbs, promisify); - angular.forEach(eventNames, wrapEvent); - - return socket; + this.base = ''; + this.config = {}; + this.debug = false; + + // like https://docs.angularjs.org/api/ng/service/$http#interceptors + // but with sails.io arguments + var interceptorFactories = this.interceptors = [ + /*function($injectables) { + return { + request: function(config) {}, + response: function(response) {}, + requestError: function(config) {}, + responseError: function(response) {} + }; + }*/ + ]; + + this.$get = ['$q', '$injector', '$rootScope', '$log', function($q, $injector, $rootScope, $log) { + var socket = (io.sails || io).connect(provider.url, provider.config); + + // build interceptor chain + var reversedInterceptors = []; + angular.forEach(interceptorFactories, function(interceptorFactory) { + reversedInterceptors.unshift( + angular.isString(interceptorFactory) ? + $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); + }); + + // Send the request using the socket + function serverRequest(config) { + var defer = $q.defer(); + if(provider.debug) $log.info('$sails ' + config.method.toUpperCase() + ' ' + config.url, config.data || ''); + + socket['legacy_' + config.method](config.url, config.data, function(result, jwr) { + // resolve promise if JSON web response is an object that has a statusCode 2xx + jwr.data = result; // backward compat, jwr.body also holds your data + jwr.socket = socket; + jwr.url = config.url; + jwr.method = config.method; + jwr.config = config.config; + if(!jwr || !angular.isObject(jwr) || !jwr.statusCode || jwr.statusCode < 200 || jwr.statusCode >= 300) { + if(provider.debug) $log.warn('$sails response ' + jwr.statusCode + ' ' + config.url, jwr); + defer.reject(jwr); + } else { + if(provider.debug) $log.info('$sails response ' + config.url, jwr); + defer.resolve(jwr); + } + }); + return defer.promise; + } + + // Wrap a socket.io method within the promis chain + function promisify(methodName) { + socket['legacy_' + methodName] = socket[methodName]; + + socket[methodName] = function(url, data, config) { + + var chain = [serverRequest, undefined]; + var promise = $q.when({ + url: provider.base + url, + data: data, + socket: socket, + config: config || {}, + method: methodName + }); + + // apply interceptors + angular.forEach(reversedInterceptors, function(interceptor) { + if(interceptor.request || interceptor.requestError) { + chain.unshift(interceptor.request, interceptor.requestError); + } + if(interceptor.response || interceptor.responseError) { + chain.push(interceptor.response, interceptor.responseError); + } + }); + + while(chain.length) { + var thenFn = chain.shift(); + var rejectFn = chain.shift(); + + promise = promise.then(thenFn, rejectFn); + } + + return promise; + }; + } + + + // Wrap events to ensure a $digest cycle + function wrapEvent(eventName) { + socket['legacy_' + eventName] = socket[eventName]; + socket[eventName] = function(event, cb) { + if(cb !== null && angular.isFunction(cb)) { + socket['legacy_' + eventName](event, function(result) { + $rootScope.$evalAsync(cb.bind(socket, result)); + }); + } + }; + } + + + angular.forEach(provider.httpVerbs, promisify); + angular.forEach(provider.eventNames, wrapEvent); + + + /** + * Update a model on sails pushes + * @param {string} name Sails model name + * @param {objcet} modelObj angular model object + * @todo remove the lodash dependency + */ + socket.$modelUpdater = function(name, modelObj) { + + socket.on(name, function(message) { + switch(message.verb) { + + case "created": + // create new model item + modelObj.push(message.data); + break; + + case "updated": + var obj = _.find(modelObj, {id: parseInt(message.id, 10)}); + + // 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 = _.clone(message.previous); + modelObj.push(obj); + } + + // update the model item + _.merge(obj, message.data); + break; + + case "destroyed": + _.remove(modelObj, {id: parseInt(message.id, 10)}); + break; + } + }); + }; + + return socket; }]; -}); + }); +}(angular, io)); From c049839c03ebc46e6c6d333d94cffe463704eebf Mon Sep 17 00:00:00 2001 From: Christoph Wiechert Date: Wed, 21 Jan 2015 20:00:06 +0100 Subject: [PATCH 3/6] Added WebStorms .idea to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7ba97ec..63746a5 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ results npm-debug.log node_modules -.tern-project \ No newline at end of file +.tern-project +/.idea/ From acd6afbfdc816d79f197df2dc7442c15273e23c3 Mon Sep 17 00:00:00 2001 From: Christoph Wiechert Date: Wed, 21 Jan 2015 20:02:31 +0100 Subject: [PATCH 4/6] Readded $http compatible success(),error() --- src/service/angular-sails.js | 55 ++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/service/angular-sails.js b/src/service/angular-sails.js index c49929c..7a9b612 100644 --- a/src/service/angular-sails.js +++ b/src/service/angular-sails.js @@ -3,6 +3,42 @@ io.sails.autoConnect = false; window.io.sails.useCORSRouteToGetCookie = false; + // copied from angular + function parseHeaders(headers) { + var parsed = Object.create(null), key, val, i; + if(!headers) return parsed; + forEach(headers.split('\n'), function(line) { + i = line.indexOf(':'); + key = lowercase(trim(line.substr(0, i))); + val = trim(line.substr(i + 1)); + if(key) { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + }); + + return parsed; + } + function trim(value) { + return angular.isString(value) ? value.trim() : value; + } + + // copied from angular + function headersGetter(headers) { + var headersObj = angular.isObject(headers) ? headers : undefined; + return function(name) { + if(!headersObj) headersObj = parseHeaders(headers); + if(name) { + var value = headersObj[lowercase(name)]; + if(value === void 0) { + value = null; + } + return value; + } + return headersObj; + }; + } + + /*global angular */ angular.module('ngSails', ['ng']); @@ -52,10 +88,11 @@ socket['legacy_' + config.method](config.url, config.data, function(result, jwr) { // resolve promise if JSON web response is an object that has a statusCode 2xx - jwr.data = result; // backward compat, jwr.body also holds your data + jwr.data = result; // backward and $http compat + jwr.status = jwr.statusCode; // $http compat jwr.socket = socket; jwr.url = config.url; - jwr.method = config.method; + jwr.method = config.method.toUpperCase(); jwr.config = config.config; if(!jwr || !angular.isObject(jwr) || !jwr.statusCode || jwr.statusCode < 200 || jwr.statusCode >= 300) { if(provider.debug) $log.warn('$sails response ' + jwr.statusCode + ' ' + config.url, jwr); @@ -100,6 +137,20 @@ promise = promise.then(thenFn, rejectFn); } + // be $http compatible + promise.success = function(fn) { + promise.then(function(jwr) { + fn(jwr.body, jwr.statusCode, headersGetter(jwr.headers), jwr); + }); + return promise; + }; + promise.error = function(fn) { + promise.then(null, function(jwr) { + fn(jwr.body, jwr.statusCode, headersGetter(jwr.headers), jwr); + }); + return promise; + }; + return promise; }; } From af82c647aa6f3219a4d3ed692ba14793f91ae594 Mon Sep 17 00:00:00 2001 From: Christoph Wiechert Date: Wed, 21 Jan 2015 20:04:09 +0100 Subject: [PATCH 5/6] Some config improvements, changed transports to first try websocket instead of polling --- src/service/angular-sails.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/service/angular-sails.js b/src/service/angular-sails.js index 7a9b612..6b67672 100644 --- a/src/service/angular-sails.js +++ b/src/service/angular-sails.js @@ -1,7 +1,6 @@ (function(angular, io) { 'use strict'; io.sails.autoConnect = false; - window.io.sails.useCORSRouteToGetCookie = false; // copied from angular function parseHeaders(headers) { @@ -52,9 +51,17 @@ // wrap these events in $evalAsync to fire a digest cycle this.eventNames = ['on', 'once']; + // the url to connect to this.url = undefined; - this.base = ''; - this.config = {}; + + // Prefix every request with this url + this.urlPrefix = ''; + + this.config = { + // Transports to use when communicating with the server, in the order they will be tried + transports: ['websocket', 'polling'], + useCORSRouteToGetCookie: false + }; this.debug = false; // like https://docs.angularjs.org/api/ng/service/$http#interceptors @@ -88,13 +95,14 @@ socket['legacy_' + config.method](config.url, config.data, function(result, jwr) { // resolve promise if JSON web response is an object that has a statusCode 2xx + if(!jwr) jwr = {}; jwr.data = result; // backward and $http compat jwr.status = jwr.statusCode; // $http compat jwr.socket = socket; jwr.url = config.url; jwr.method = config.method.toUpperCase(); jwr.config = config.config; - if(!jwr || !angular.isObject(jwr) || !jwr.statusCode || jwr.statusCode < 200 || jwr.statusCode >= 300) { + if(jwr.error || jwr.statusCode < 200 || jwr.statusCode >= 300) { if(provider.debug) $log.warn('$sails response ' + jwr.statusCode + ' ' + config.url, jwr); defer.reject(jwr); } else { @@ -113,7 +121,7 @@ var chain = [serverRequest, undefined]; var promise = $q.when({ - url: provider.base + url, + url: provider.urlPrefix + url, data: data, socket: socket, config: config || {}, From 39765c6a4c8b2202ffe335b063d45f0e326174ff Mon Sep 17 00:00:00 2001 From: Christoph Wiechert Date: Wed, 21 Jan 2015 20:04:32 +0100 Subject: [PATCH 6/6] Removed lodash dependency --- src/service/angular-sails.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/service/angular-sails.js b/src/service/angular-sails.js index 6b67672..682ef71 100644 --- a/src/service/angular-sails.js +++ b/src/service/angular-sails.js @@ -183,22 +183,28 @@ /** * Update a model on sails pushes - * @param {string} name Sails model name - * @param {objcet} modelObj angular model object - * @todo remove the lodash dependency + * @param {String} name Sails model name + * @param {Array} models Array with model objects */ - socket.$modelUpdater = function(name, modelObj) { + socket.$modelUpdater = function(name, models) { socket.on(name, function(message) { + var i; switch(message.verb) { case "created": // create new model item - modelObj.push(message.data); + models.push(message.data); break; case "updated": - var obj = _.find(modelObj, {id: parseInt(message.id, 10)}); + var obj; + for(i=0; i