diff --git a/common/script/ops/clearPMs.js b/common/script/ops/clearPMs.js index 765ecc3b56..537ef26348 100644 --- a/common/script/ops/clearPMs.js +++ b/common/script/ops/clearPMs.js @@ -1,6 +1,6 @@ module.exports = function clearPMs (user) { user.inbox.messages = {}; - user.markModified('inbox.messages'); + // user.markModified('inbox.messages'); return [ user.inbox.messages, ]; diff --git a/common/script/public/userServices.js b/common/script/public/userServices.js deleted file mode 100644 index 4fb92ce42a..0000000000 --- a/common/script/public/userServices.js +++ /dev/null @@ -1,268 +0,0 @@ -'use strict'; - -angular.module('habitrpg') - .service('ApiUrl', ['API_URL', function(currentApiUrl){ - this.setApiUrl = function(newUrl){ - currentApiUrl = newUrl; - }; - - this.get = function(){ - return currentApiUrl; - }; - }]) - -/** - * Services that persists and retrieves user from localStorage. - */ - .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'MOBILE_APP', 'Notification', 'ApiUrl', - function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, MOBILE_APP, Notification, ApiUrl) { - 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 - - var userNotifications = { - // "party.order" : env.t("updatedParty"), - // "party.orderAscending" : env.t("updatedParty") - // party.order notifications are not currently needed because the party avatars are resorted immediately now - }; // this is a list of notifications to send to the user when changes are made, along with the message. - - //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()); - }); - - // Save the current filters - var current_filters = user.filters; - - $http.post(ApiUrl.get() + '/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 && data.wasModified) { - delete data.wasModified; - $rootScope.$emit('userUpdated', user); - } - - // Update user - _.extend(user, data); - // Preserve filter selections between syncs - _.extend(user.filters,current_filters); - if (!user._wrapped){ - - // This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client, - // they update the user in the browser and then send the request to the server, where the same operation is - // replicated. We need to wrap each op to provide a callback to send that operation - $window.habitrpgShared.wrap(user); - _.each(user.ops, function(op,k){ - user.ops[k] = function(req,cb){ - if (cb) return op(req,cb); - op(req,function(err,response) { - for(var updatedItem in req.body) { - var itemUpdateResponse = userNotifications[updatedItem]; - if(itemUpdateResponse) Notification.text(itemUpdateResponse.data.message); - } - if (err) { - var message = err.code ? err.data.message : err; - if (MOBILE_APP) Notification.push({type:'text', text: message}); - else Notification.text(message); - // In the case of 200s, they're friendly alert messages like "Your 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) { - // (Notifications handled in app.js) - - // If we're offline, queue up offline actions so we can send when we're back online - if (status === 0) { - //move sent actions back to queue - _.times(sent.length, function () { - queue.push(sent.shift()) - }); - settings.fetching = false; - // In the case of errors, discard the corrupt queue - } else { - // 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(); - } - }); - } - - - 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) { - var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) - $http.defaults.headers.common['x-api-user'] = uuid; - $http.defaults.headers.common['x-api-key'] = token; - $http.defaults.headers.common['x-user-timezoneOffset'] = offset; - 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 - 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 !== ""; - }, - - getBalanceInGems: function() { - var balance = user.balance || 0; - return balance * 4; - }, - - 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 { - var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/); - if (!isStaticOrSocial){ - localStorage.clear(); - $window.location.href = '/logout'; - } - } - } - - } else { - userServices.authenticate(settings.auth.apiId, settings.auth.apiToken) - } - - return userServices; - } -]); diff --git a/website/public/js/controllers/copyMessageModalCtrl.js b/website/public/js/controllers/copyMessageModalCtrl.js index 60d07ec152..1e58237454 100644 --- a/website/public/js/controllers/copyMessageModalCtrl.js +++ b/website/public/js/controllers/copyMessageModalCtrl.js @@ -9,7 +9,7 @@ habitrpg.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification', notes: $scope.notes }; - User.user.ops.addTask({body:newTask}); + User.addTask({body:newTask}); Notification.text(window.env.t('messageAddedAsToDo')); $scope.$close(); diff --git a/website/public/js/controllers/filtersCtrl.js b/website/public/js/controllers/filtersCtrl.js index cfdc45658d..e2597af241 100644 --- a/website/public/js/controllers/filtersCtrl.js +++ b/website/public/js/controllers/filtersCtrl.js @@ -14,7 +14,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', _.each(User.user.tags, function(tag){ // Send an update op for each changed tag (excluding new tags & deleted tags, this if() packs a punch) if (tagsSnap[tag.id] && tagsSnap[tag.id].name != tag.name) - User.user.ops.updateTag({params:{id:tag.id},body:{name:tag.name}}); + User.updateTag({params:{id:tag.id},body:{name:tag.name}}); }) $scope._editing = false; } else { @@ -37,7 +37,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', $scope.updateTaskFilter(); $scope.createTag = function() { - User.user.ops.addTag({body:{name:$scope._newTag.name, id:Shared.uuid()}}); + User.addTag({body:{name:$scope._newTag.name, id:Shared.uuid()}}); $scope._newTag.name = ''; }; }]); diff --git a/website/public/js/controllers/footerCtrl.js b/website/public/js/controllers/footerCtrl.js index 42fd279944..7307adaee7 100644 --- a/website/public/js/controllers/footerCtrl.js +++ b/website/public/js/controllers/footerCtrl.js @@ -78,6 +78,7 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { }); }; + //@TODO: Route? $scope.addMissedDay = function(numberOfDays){ if (!confirm("Are you sure you want to reset the day by " + numberOfDays + " day(s)?")) return; var dayBefore = moment(User.user.lastCron).subtract(numberOfDays, 'days').toDate(); @@ -86,15 +87,12 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { }; $scope.addTenGems = function(){ - $http.post(ApiUrl.get() + '/api/v2/user/addTenGems').success(function(){ - User.log({}); - }) + User.addTenGems(); }; $scope.addHourglass = function(){ - $http.post(ApiUrl.get() + '/api/v2/user/addHourglass').success(function(){ - User.log({}); - }) + User.addHourglass(); + //User.log({}); }; $scope.addGold = function(){ @@ -124,6 +122,7 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { }; $scope.addBossQuestProgressUp = function(){ + //@TODO: Route? User.set({ 'party.quest.progress.up': User.user.party.quest.progress.up + 1000 }); diff --git a/website/public/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js index f932d62cb6..238cf75c49 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -68,7 +68,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.deleteAllMessages = function() { if (confirm(window.env.t('confirmDeleteAllMessages'))) { - User.user.ops.clearPMs({}); + User.clearPMs(); } }; diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index d0a80d67d5..761e708b37 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -99,7 +99,7 @@ habitrpg.controller("InventoryCtrl", var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined; if (selected) { var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined; - user.ops.sell({params:{type:type, key: $scope[selected].key}}); + User.sell({params:{type:type, key: $scope[selected].key}}); if (user.items[type][$scope[selected].key] < 1) { $scope[selected] = null; } @@ -118,7 +118,7 @@ habitrpg.controller("InventoryCtrl", var userHasPet = user.items.pets[egg.key + '-' + potion.key] > 0; var isPremiumPet = Content.hatchingPotions[potion.key].premium && !Content.dropEggs[egg.key]; - user.ops.hatch({params:{egg:egg.key, hatchingPotion:potion.key}}); + User.hatch({params:{egg:egg.key, hatchingPotion:potion.key}}); if (!user.preferences.suppressModals.hatchPet && !userHasPet && !isPremiumPet) { $scope.hatchedPet = { @@ -172,7 +172,7 @@ habitrpg.controller("InventoryCtrl", } else if (!$window.confirm(window.env.t('feedPet', {name: petDisplayName, article: food.article, text: food.text()}))) { return; } - User.user.ops.feed({params:{pet: pet, food: food.key}}); + User.feed({params:{pet: pet, food: food.key}}); $scope.selectedFood = null; _updateDropAnimalCount(user.items); @@ -198,12 +198,12 @@ habitrpg.controller("InventoryCtrl", // Selecting Pet } else { - User.user.ops.equip({params:{type: 'pet', key: pet}}); + User.equip({params:{type: 'pet', key: pet}}); } } $scope.chooseMount = function(egg, potion) { - User.user.ops.equip({params:{type: 'mount', key: egg + '-' + potion}}); + User.equip({params:{type: 'mount', key: egg + '-' + potion}}); } $scope.getSeasonalShopArray = function(set){ @@ -230,7 +230,7 @@ habitrpg.controller("InventoryCtrl", for (item in user.items.gear.equipped){ var itemKey = user.items.gear.equipped[item]; if (user.items.gear.owned[itemKey]) { - user.ops.equip({params: {key: itemKey}}); + User.equip({params: {key: itemKey}}); } } break; @@ -239,7 +239,7 @@ habitrpg.controller("InventoryCtrl", for (item in user.items.gear.costume){ var itemKey = user.items.gear.costume[item]; if (user.items.gear.owned[itemKey]) { - user.ops.equip({params: {type:"costume", key: itemKey}}); + User.equip({params: {type:"costume", key: itemKey}}); } } break; @@ -247,17 +247,17 @@ habitrpg.controller("InventoryCtrl", case "petMountBackground": var pet = user.items.currentPet; if (pet) { - user.ops.equip({params:{type: 'pet', key: pet}}); + User.equip({params:{type: 'pet', key: pet}}); } var mount = user.items.currentMount; if (mount) { - user.ops.equip({params:{type: 'mount', key: mount}}); + User.equip({params:{type: 'mount', key: mount}}); } var background = user.preferences.background; if (background) { - User.user.ops.unlock({query:{path:"background."+background}}); + User.unlock({query:{path:"background."+background}}); } break; @@ -310,9 +310,9 @@ habitrpg.controller("InventoryCtrl", }; $scope.clickTimeTravelItem = function(type,key) { - if (user.purchased.plan.consecutive.trinkets < 1) return user.ops.hourglassPurchase({params:{type:type,key:key}}); + if (user.purchased.plan.consecutive.trinkets < 1) return User.hourglassPurchase({params:{type:type,key:key}}); if (!window.confirm(window.env.t('hourglassBuyItemConfirm'))) return; - user.ops.hourglassPurchase({params:{type:type,key:key}}); + User.hourglassPurchase({params:{type:type,key:key}}); }; function _updateDropAnimalCount(items) { diff --git a/website/public/js/controllers/partyCtrl.js b/website/public/js/controllers/partyCtrl.js index 4ed77885f7..cfac272b34 100644 --- a/website/public/js/controllers/partyCtrl.js +++ b/website/public/js/controllers/partyCtrl.js @@ -29,7 +29,7 @@ habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User',' $scope.newGroup = { type: 'party' }; }); } - // Chat.seenMessage($scope.group._id); + function checkForNotifications () { // Checks if user's party has reached 2 players for the first time. if(!user.achievements.partyUp diff --git a/website/public/js/controllers/rootCtrl.js b/website/public/js/controllers/rootCtrl.js index eacc0d2cce..a4025ad23c 100644 --- a/website/public/js/controllers/rootCtrl.js +++ b/website/public/js/controllers/rootCtrl.js @@ -21,7 +21,8 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name}); // clear inbox when entering or exiting inbox tab if (fromState.name=='options.social.inbox' || toState.name=='options.social.inbox') { - User.user.ops.update && User.set({'inbox.newMessages':0}); + //@TODO: Protected path. We need a url + User.set({'inbox.newMessages': 0}); } }); @@ -218,11 +219,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ key: itemKey }; - user.ops.equip({ params: equipParams }); + User.equip({ params: equipParams }); } $rootScope.purchase = function(type, item){ - if (type == 'special') return user.ops.buySpecialSpell({params:{key:item.key}}); + if (type == 'special') return User.buySpecialSpell({params:{key:item.key}}); var gems = user.balance * 4; var price = item.value; @@ -248,7 +249,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ message += window.env.t('buyThis', {text: itemName, price: price, gems: gems}); if ($window.confirm(message)) - user.ops.purchase({params:{type:type,key:item.key}}); + User.purchase({params:{type:type,key:item.key}}); }; function _canBuyEquipment(itemKey) { diff --git a/website/public/js/controllers/settingsCtrl.js b/website/public/js/controllers/settingsCtrl.js index 24fb8a03dc..a93dde0fb3 100644 --- a/website/public/js/controllers/settingsCtrl.js +++ b/website/public/js/controllers/settingsCtrl.js @@ -99,7 +99,7 @@ habitrpg.controller('SettingsCtrl', $scope.popoverEl.popover('destroy'); if (confirm) { - User.user.ops.reroll({}); + User.reroll({}); $rootScope.$state.go('tasks'); } } @@ -124,7 +124,7 @@ habitrpg.controller('SettingsCtrl', $scope.popoverEl.popover('destroy'); if (confirm) { - User.user.ops.rebirth({}); + User.rebirth({}); $rootScope.$state.go('tasks'); } } @@ -175,7 +175,7 @@ habitrpg.controller('SettingsCtrl', } $scope.reset = function(){ - User.user.ops.reset({}); + User.reset({}); $rootScope.$state.go('tasks'); } @@ -235,7 +235,7 @@ habitrpg.controller('SettingsCtrl', var releaseFunction = RELEASE_ANIMAL_TYPES[type]; if (releaseFunction) { - User.user.ops[releaseFunction]({}); + User[releaseFunction]({}); $rootScope.$state.go('tasks'); } } @@ -246,15 +246,15 @@ habitrpg.controller('SettingsCtrl', $scope.hasWebhooks = _.size(webhooks); }) $scope.addWebhook = function(url) { - User.user.ops.addWebhook({body:{url:url, id:Shared.uuid()}}); + User.addWebhook({body:{url:url, id:Shared.uuid()}}); $scope._newWebhook.url = ''; } $scope.saveWebhook = function(id,webhook) { delete webhook._editing; - User.user.ops.updateWebhook({params:{id:id}, body:webhook}); + User.updateWebhook({params:{id:id}, body:webhook}); } $scope.deleteWebhook = function(id) { - User.user.ops.deleteWebhook({params:{id:id}}); + User.deleteWebhook({params:{id:id}}); } $scope.applyCoupon = function(coupon){ diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js index 5e86a243e3..857f1e2393 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -24,7 +24,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N if (direction === 'down') $rootScope.playSound('Minus_Habit'); else if (direction === 'up') $rootScope.playSound('Plus_Habit'); } - User.user.ops.score({params:{id: task.id, direction:direction}}); + User.score({params:{id: task.id, direction:direction}}); Analytics.updateUser(); Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'score task','taskType':task.type,'direction':direction}); }; @@ -38,7 +38,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N }), }; - User.user.ops.addTask({body:newTask}); + User.addTask({body:newTask}); } $scope.addTask = function(addTo, listDef) { @@ -80,7 +80,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N */ $scope.pushTask = function(task, index, location) { var to = (location === 'bottom' || $scope.ctrlPressed) ? -1 : 0; - User.user.ops.sortTask({params:{id:task.id},query:{from:index, to:to}}) + User.sortTask({params:{id:task.id},query:{from:index, to:to}}) }; /** @@ -96,18 +96,13 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.removeTask = function(task) { if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return; - User.user.ops.deleteTask({params:{id:task.id}}) + User.deleteTask({params:{id:task.id}}) }; $scope.saveTask = function(task, stayOpen, isSaveAndClose) { - //@TODO: We will need to fix tag saving when user service is ported since tags are attached at the user level - - if (task.checklist) { - task.checklist = _.filter(task.checklist, function(i) {return !!i.text}); - } - - User.user.ops.updateTask({params:{id:task.id},body:task}); - + if (task.checklist) + task.checklist = _.filter(task.checklist,function(i){return !!i.text}); + User.updateTask({params:{id:task.id},body:task}); if (!stayOpen) task._editing = false; if (isSaveAndClose) { @@ -177,10 +172,10 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N if (!task.checklist[$index].text) { // Don't allow creation of an empty checklist item // TODO Provide UI feedback that this item is still blank - } else if ($index == task.checklist.length - 1) { - User.user.ops.updateTask({params:{id:task.id},body:task}); - task.checklist.push({completed: false, text: ''}); - focusChecklist(task, task.checklist.length - 1); + } else if ($index == task.checklist.length-1){ + User.updateTask({params:{id:task.id},body:task}); // don't preen the new empty item + task.checklist.push({completed:false,text:''}); + focusChecklist(task,task.checklist.length-1); } else { $scope.saveTask(task, true); focusChecklist(task, $index + 1); @@ -238,7 +233,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.buy = function(item) { playRewardSound(item); - User.user.ops.buy({params:{key:item.key}}); + User.buy({params:{key:item.key}}); }; /* diff --git a/website/public/js/controllers/userCtrl.js b/website/public/js/controllers/userCtrl.js index e345d8ba2e..b62207b945 100644 --- a/website/public/js/controllers/userCtrl.js +++ b/website/public/js/controllers/userCtrl.js @@ -17,17 +17,17 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ }); $scope.allocate = function(stat){ - User.user.ops.allocate({query:{stat:stat}}); + User.allocate({query:{stat:stat}}); } $scope.changeClass = function(klass){ if (!klass) { if (!confirm(window.env.t('sureReset'))) return; - return User.user.ops.changeClass({}); + return User.changeClass({}); } - User.user.ops.changeClass({query:{class:klass}}); + User.changeClass({query:{class:klass}}); $scope.selectedClass = undefined; Shared.updateStore(User.user); Guide.goto('classes', 0,true); @@ -46,7 +46,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ } $scope.acknowledgeHealthWarning = function(){ - User.user.ops.update && User.set({'flags.warnedLowHealth':true}); + User.set({'flags.warnedLowHealth':true}); } /** @@ -69,7 +69,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return; if (User.user.balance < cost) return $rootScope.openModal('buyGems'); } - User.user.ops.unlock({query:{path:path}}) + User.unlock({query:{path:path}}) } $scope.ownsSet = function(type,_set) { diff --git a/website/public/js/services/userServices.js b/website/public/js/services/userServices.js new file mode 100644 index 0000000000..ea0f706d2e --- /dev/null +++ b/website/public/js/services/userServices.js @@ -0,0 +1,416 @@ +'use strict'; + +angular.module('habitrpg') + .service('ApiUrl', ['API_URL', function(currentApiUrl) { + this.setApiUrl = function(newUrl){ + currentApiUrl = newUrl; + }; + + this.get = function(){ + return currentApiUrl; + }; + }]) + +/** + * Services that persists and retrieves user from localStorage. + */ + .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'MOBILE_APP', 'Notification', 'ApiUrl', + function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, MOBILE_APP, Notification, ApiUrl) { + 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 + + var userNotifications = { + // "party.order" : env.t("updatedParty"), + // "party.orderAscending" : env.t("updatedParty") + // party.order notifications are not currently needed because the party avatars are resorted immediately now + }; // this is a list of notifications to send to the user when changes are made, along with the message. + + //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; + + function sync() { + $http({ + method: "GET", + url: 'api/v3/user/', + }) + .then(function (response) { + Notification.text(response.data.message); + + _.extend(user, response.data.data); + + if (!user._wrapped) { + // This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client, + // they update the user in the browser and then send the request to the server, where the same operation is + // replicated. We need to wrap each op to provide a callback to send that operation + $window.habitrpgShared.wrap(user); + _.each(user.ops, function(op,k){ + user.ops[k] = function(req,cb){ + if (cb) return op(req,cb); + op(req,function(err,response) { + for(var updatedItem in req.body) { + var itemUpdateResponse = userNotifications[updatedItem]; + if(itemUpdateResponse) Notification.text(itemUpdateResponse); + } + if (err) { + var message = err.code ? err.message : err; + if (MOBILE_APP) Notification.push({type:'text',text:message}); + else Notification.text(message); + // In the case of 200s, they're friendly alert messages like "Your 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}); + }); + } + }); + } + + save(); + $rootScope.$emit('userSynced'); + }) + } + sync(); + + var save = function () { + localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user)); + localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings)); + }; + + function callOpsFunctionAndRequest (opName, endPoint, method, paramString, opData) { + if (!opData) opData = {}; + $window.habitrpgShared.ops[opName](user, opData); + + var url = 'api/v3/user/' + endPoint; + if (paramString) { + url += '/' + paramString + } + + var body = {}; + if (opData.body) body = opData.body; + + var queryString = ''; + if (opData.query) queryString = '?' + $.param(opData.query) + + $http({ + method: method, + url: url + queryString, + body: body, + }) + .then(function (response) { + Notification.text(response.data.message); + save(); + }) + } + + function setUser(updates) { + for (var key in updates) { + user[key] = updates[key]; + } + + sync(); + } + + var userServices = { + user: user, + + allocate: function (data) { + callOpsFunctionAndRequest('allocate', 'allocate', "POST",'', data); + }, + + changeClass: function (data) { + callOpsFunctionAndRequest('changeClass', 'change-class', "POST",'', data); + }, + + addTask: function (data) { + //@TODO: Should this been on habitrpgShared? + user.ops.addTask(data); + save(); + //@TODO: Call task service when PR is merged + }, + + score: function (data) { + user.ops.scoreTask(data); + save(); + //@TODO: Call task service when PR is merged + }, + + sortTask: function (data) { + user.ops.sortTask(data); + save(); + //@TODO: Call task service when PR is merged + }, + + updateTask: function (data) { + user.ops.updateTask(data); + save(); + //@TODO: Call task service when PR is merged + }, + + deleteTask: function (data) { + user.ops.deleteTask(data); + save(); + //@TODO: Call task service when PR is merged + }, + + addTag: function(data) { + user.ops.addTag(data); + save(); + //@TODO: Call task service when PR is merged + }, + + updateTag: function(data) { + user.ops.updateTag(data); + save(); + //@TODO: Call task service when PR is merged + }, + + addTenGems: function () { + $http({ + method: "POST", + url: 'api/v3/debug/add-ten-gems', + }) + .then(function (response) { + Notification.text('+10 Gems!'); + sync(); + }) + }, + + addHourglass: function () { + $http({ + method: "POST", + url: 'api/v3/debug/add-hourglass', + }) + .then(function (response) { + sync(); + }) + }, + + clearPMs: function () { + callOpsFunctionAndRequest('clearPMs', 'messages', "DELETE"); + }, + + buy: function (data) { + callOpsFunctionAndRequest('buy', 'buy', "POST", data.params.key, data); + }, + + purchase: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('purchase', 'purchase', "POST", type + '/' + key, data); + }, + + buySpecialSpell: function (data) { + $window.habitrpgShared.ops['buySpecialSpell'](user, data); + var key = data.params.key; + + $http({ + method: "POST", + url: 'api/v3/user/' + 'buy-special-spell/' + key, + }) + .then(function (response) { + Notification.text(response.data.message); + }) + }, + + sell: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('sell', 'sell', "POST", type + '/' + key, data); + }, + + hatch: function (data) { + var egg = data.params.egg; + var hatchingPotion = data.params.hatchingPotion; + callOpsFunctionAndRequest('hatch', 'hatch', "POST", egg + '/' + hatchingPotion, data); + }, + + feed: function (data) { + var pet = data.params.pet; + var food = data.params.food; + callOpsFunctionAndRequest('feed', 'feed', "POST", pet + '/' + food, data); + }, + + equip: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('equip', 'equip', "POST", type + '/' + key, data); + }, + + hourglassPurchase: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('hourglassPurchase', 'purchase-hourglass', "POST", type + '/' + key, data); + }, + + unlock: function (data) { + $window.habitrpgShared.ops['unlock'](user, data); + callOpsFunctionAndRequest('unlock', 'unlock', "POST", '', data); + }, + + set: function(updates) { + setUser(updates); + }, + + reroll: function () { + callOpsFunctionAndRequest('reroll', 'reroll', "POST"); + }, + + rebirth: function () { + callOpsFunctionAndRequest('rebirth', 'rebirth', "POST"); + }, + + reset: function () { + callOpsFunctionAndRequest('reset', 'reset', "POST"); + }, + + releaseBoth: function () { + callOpsFunctionAndRequest('releaseBoth', 'releaseBoth', "POST"); + }, + + releaseMounts: function () { + callOpsFunctionAndRequest('releaseMounts', 'releaseMounts', "POST"); + }, + + releasePets: function () { + callOpsFunctionAndRequest('releasePets', 'releasePets', "POST"); + }, + + addWebhook: function (data) { + callOpsFunctionAndRequest('addWebhook', 'webhook', "POST", '', data, data.body); + }, + + updateWebhook: function (data) { + callOpsFunctionAndRequest('updateWebhook', 'webhook', "PUT", data.params.id, data, data.body); + }, + + deleteWebhook: function (data) { + callOpsFunctionAndRequest('deleteWebhook', 'webhook', "DELETE", data.params.id, data, data.body); + }, + + sleep: function () { + callOpsFunctionAndRequest('sleep', 'sleep', "POST"); + }, + + online: function (status) { + if (status===true) { + settings.online = true; + // syncQueue(); + } else { + settings.online = false; + }; + }, + + authenticate: function (uuid, token, cb) { + if (!!uuid && !!token) { + var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) + $http.defaults.headers.common['x-api-user'] = uuid; + $http.defaults.headers.common['x-api-key'] = token; + $http.defaults.headers.common['x-user-timezoneOffset'] = offset; + 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 + 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 !== ""; + }, + + getBalanceInGems: function() { + var balance = user.balance || 0; + return balance * 4; + }, + + 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({}); + sync(); + }, + + 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 { + var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/); + if (!isStaticOrSocial){ + localStorage.clear(); + $window.location.href = '/logout'; + } + } + } + + } else { + userServices.authenticate(settings.auth.apiId, settings.auth.apiToken) + } + + return userServices; + } +]); diff --git a/website/public/manifest.json b/website/public/manifest.json index a86740e8e7..cb3db7a968 100644 --- a/website/public/manifest.json +++ b/website/public/manifest.json @@ -33,7 +33,7 @@ "bower_components/jquery-ui/ui/minified/jquery.ui.sortable.min.js", "bower_components/smart-app-banner/smart-app-banner.js", - "common/dist/scripts/habitrpg-shared.js", + "common/dist/scripts/habitrpg-shared.js", "js/env.js", @@ -42,7 +42,6 @@ "js/services/sharedServices.js", "js/services/notificationServices.js", - "common/script/public/userServices.js", "common/script/public/directives.js", "js/services/analyticsServices.js", "js/services/groupServices.js", @@ -55,6 +54,7 @@ "js/services/questServices.js", "js/services/socialServices.js", "js/services/statServices.js", + "js/services/userServices.js", "js/filters/money.js", "js/filters/roundLargeNumbers.js", @@ -132,7 +132,7 @@ "js/services/sharedServices.js", "js/services/socialServices.js", "js/services/statServices.js", - "common/script/public/userServices.js", + "js/services/userServices.js", "js/controllers/authCtrl.js", "js/controllers/footerCtrl.js" ], @@ -166,7 +166,7 @@ "js/services/sharedServices.js", "js/services/socialServices.js", "js/services/statServices.js", - "common/script/public/userServices.js", + "js/services/userServices.js", "js/controllers/authCtrl.js", "js/controllers/footerCtrl.js" ], diff --git a/website/views/options/social/tavern.jade b/website/views/options/social/tavern.jade index ed7e4ef6c3..d0f16c187d 100644 --- a/website/views/options/social/tavern.jade +++ b/website/views/options/social/tavern.jade @@ -16,7 +16,7 @@ .popover-content span(ng-if='!env.worldDmg.tavern') {{user.preferences.sleep ? env.t('innText',{name: user.profile.name}) : env.t('danielText')}} span(ng-if='env.worldDmg.tavern') {{user.preferences.sleep ? env.t('innTextBroken',{name: user.profile.name}) : env.t('danielTextBroken')}} - button.btn-block.btn.btn-lg.btn-success(ng-click='User.user.ops.sleep({})') + button.btn-block.btn.btn-lg.btn-success(ng-click='User.sleep({})') | {{user.preferences.sleep ? env.t('innCheckOut') : env.t('innCheckIn')}} span(ng-if='!user.preferences.sleep && !env.worldDmg.tavern')=env.t('danielText2') span(ng-if='!user.preferences.sleep && env.worldDmg.tavern')=env.t('danielText2Broken') diff --git a/website/views/shared/tasks/index.jade b/website/views/shared/tasks/index.jade index fc3223b888..f128b0b322 100644 --- a/website/views/shared/tasks/index.jade +++ b/website/views/shared/tasks/index.jade @@ -23,7 +23,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") i.glyphicon.glyphicon-warning-sign   =env.t('dailiesRestingInInn') - button.btn-block.btn.btn-lg.btn-success(ng-click='User.user.ops.sleep({})') + button.btn-block.btn.btn-lg.btn-success(ng-click='User.sleep({})') | {{env.t('innCheckOut')}} +taskColumnTabs('top')