From 5d390561d29036f2caca7bb4ce12cf158e4337b1 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 15 Dec 2013 21:29:28 -0700 Subject: [PATCH] move userServices.js to habitrpg-shared, so mobile can share the code --- dist/habitrpg-shared.js | 4 +- script/index.coffee | 2 +- script/userServices.js | 236 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 script/userServices.js diff --git a/dist/habitrpg-shared.js b/dist/habitrpg-shared.js index 235abcf3b7..125fd6e478 100644 --- a/dist/habitrpg-shared.js +++ b/dist/habitrpg-shared.js @@ -11076,8 +11076,8 @@ var process=require("__browserify_process");(function() { return cb(null, req); }, updateTask: function(req, cb) { - var _base; - if (!(req.params.id && user.tasks[req.params.id])) { + var _base, _ref; + if (!user.tasks[(_ref = req.params) != null ? _ref.id : void 0]) { return typeof cb === "function" ? cb("Task not found") : void 0; } _.merge(user.tasks[req.params.id], req.body); diff --git a/script/index.coffee b/script/index.coffee index cd95194b96..839543a2a7 100644 --- a/script/index.coffee +++ b/script/index.coffee @@ -387,7 +387,7 @@ api.wrap = (user) -> cb null, req updateTask: (req, cb) -> - return cb?("Task not found") unless req.params.id and user.tasks[req.params.id] + return cb?("Task not found") unless user.tasks[req.params?.id] _.merge user.tasks[req.params.id], req.body user.tasks[req.params.id].markModified? 'tags' cb? null, req diff --git a/script/userServices.js b/script/userServices.js new file mode 100644 index 0000000000..d4e7931b5e --- /dev/null +++ b/script/userServices.js @@ -0,0 +1,236 @@ +'use strict'; + +/** + * Services that persists and retrieves user from localStorage. + */ + +angular.module('userServices', []). + factory('User', ['$rootScope', '$http', '$location', '$window', 'API_URL', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'MOBILE_APP', 'Notification', + function($rootScope, $http, $location, $window, API_URL, STORAGE_USER_ID, STORAGE_SETTINGS_ID, MOBILE_APP, Notification) { + var authenticated = false; + var defaultSettings = { + auth: { apiId: '', apiToken: ''}, + sync: { + queue: [], //here OT will be queued up, this is NOT call-back queue! + sent: [] //here will be OT which have been sent, but we have not got reply from server yet. + }, + fetching: false, // whether fetch() was called or no. this is to avoid race conditions + online: false + }; + var settings = {}; //habit mobile settings (like auth etc.) to be stored here + var user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate + + //first we populate user with schema + user.apiToken = user._id = ''; // we use id / apitoken to determine if registered + + //than we try to load localStorage + if (localStorage.getItem(STORAGE_USER_ID)) { + _.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID))); + } + user._wrapped = false; + + var syncQueue = function (cb) { + if (!authenticated) { + $window.alert("Not authenticated, can't sync, go to settings first."); + return; + } + + var queue = settings.sync.queue; + var sent = settings.sync.sent; + if (queue.length === 0) { + // Sync: Queue is empty + return; + } + if (settings.fetching) { + // Sync: Already fetching + return; + } + if (settings.online!==true) { + // Sync: Not online + return; + } + + settings.fetching = true; + // move all actions from queue array to sent array + _.times(queue.length, function () { + sent.push(queue.shift()); + }); + + $http.post(API_URL + '/api/v2/user/batch-update', sent, {params: {data:+new Date, _v:user._v, siteVersion: $window.env && $window.env.siteVersion}}) + .success(function (data, status, heacreatingders, config) { + //make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data. + if (!queue.length) { + //we can't do user=data as it will not update user references in all other angular controllers. + + // the user has been modified from another application, sync up + if(data.wasModified) { + delete data.wasModified; + $rootScope.$emit('userUpdated', user); + } + + // Update user + _.extend(user, data); + if (!user._wrapped){ + $window.habitrpgShared.wrap(user); + _.each(user.ops, function(op,k){ + user.ops[k] = _.partialRight(op, function(err, req){ + if (err) { + var message = err.code ? err.message : err; + console.log(message); + if (MOBILE_APP) Notification.push({type:'text',text:message}); + else Notification.text(message); + // In the case of 200s, they're friendly alert messages like "You're pet has hatched!" - still send the op + if ((err.code && err.code >= 400) || !err.code) return; + + } + userServices.log({op:k, params: req.params, query:req.query, body:req.body}); + }); + }); + } + + // Emit event when user is synced + $rootScope.$emit('userSynced'); + } + sent.length = 0; + settings.fetching = false; + save(); + if (cb) { + cb(false) + } + + syncQueue(); // call syncQueue to check if anyone pushed more actions to the queue while we were talking to server. + }) + .error(function (data, status, headers, config) { + // In the case of errors, discard the corrupt queue + if (status >= 400) { + data = + data.needRefresh ? "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again." : + data ? data : + 'Something went wrong, please refresh your browser or upgrade the mobile app'; + alert(data); + // Clear the queue. Better if we can hunt down the problem op, but this is the easiest solution + settings.sync.queue = settings.sync.sent = []; + save(); + + // But if we're offline, queue up offline actions so we can send when we're back online + } else { + //move sent actions back to queue + _.times(sent.length, function () { + queue.push(sent.shift()) + }); + settings.fetching = false; + //Notification.text("No Connection"); + } + }); + } + + + var save = function () { + localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user)); + localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings)); + }; + var userServices = { + user: user, + set: function(updates) { + user.ops.update({body:updates}); + }, + online: function (status) { + if (status===true) { + settings.online = true; + syncQueue(); + } else { + settings.online = false; + }; + }, + + authenticate: function (uuid, token, cb) { + if (!!uuid && !!token) { + $http.defaults.headers.common['x-api-user'] = uuid; + $http.defaults.headers.common['x-api-key'] = token; + authenticated = true; + settings.auth.apiId = uuid; + settings.auth.apiToken = token; + settings.online = true; + if (user && user._v) user._v--; // shortcut to always fetch new updates on page reload + userServices.log({}, function(){ + // If they don't have timezone, set it + var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) + if (user.preferences.timezoneOffset !== offset) + userServices.set({'preferences.timezoneOffset': offset}); + cb && cb(); + }); + } else { + alert('Please enter your ID and Token in settings.') + } + }, + + authenticated: function(){ + return this.settings.auth.apiId !== ""; + }, + + log: function (action, cb) { + //push by one buy one if an array passed in. + if (_.isArray(action)) { + action.forEach(function (a) { + settings.sync.queue.push(a); + }); + } else { + settings.sync.queue.push(action); + } + + save(); + syncQueue(cb); + }, + + sync: function(){ + user._v--; + userServices.log({}); + }, + + save: save, + + settings: settings + }; + + + //load settings if we have them + if (localStorage.getItem(STORAGE_SETTINGS_ID)) { + //use extend here to make sure we keep object reference in other angular controllers + _.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID))); + + //if settings were saved while fetch was in process reset the flag. + settings.fetching = false; + //create and load if not + } else { + localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings)); + _.extend(settings, defaultSettings); + } + + //If user does not have ApiID that forward him to settings. + if (!settings.auth.apiId || !settings.auth.apiToken) { + + if (MOBILE_APP) { + $location.path("/login"); + } else { + //var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=... + var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead + if (search.err) return alert(search.err); + if (search._id && search.apiToken) { + userServices.authenticate(search._id, search.apiToken, function(){ + $window.location.href='/'; + }); + } else { + if ($window.location.pathname.indexOf('/static') !== 0){ + localStorage.clear(); + $window.location.href = '/logout'; + } + } + } + + } else { + userServices.authenticate(settings.auth.apiId, settings.auth.apiToken) + } + + return userServices; + } +]);