diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index e1f9f63bfb..8a77c078b9 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -49,11 +49,11 @@ QmlCommerce::QmlCommerce() { void QmlCommerce::openSystemApp(const QString& appPath) { - QUrl appUrl = PathUtils::qmlUrl(appPath); - auto tablet = dynamic_cast( DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); - tablet->loadQMLSource(appUrl); + tablet->loadQMLSource(appPath); + + DependencyManager::get()->openTablet(); } diff --git a/server-console/resources/tray-menu-notification.png b/server-console/resources/tray-menu-notification.png index 569ee95d7e..0d6e15752f 100644 Binary files a/server-console/resources/tray-menu-notification.png and b/server-console/resources/tray-menu-notification.png differ diff --git a/server-console/src/main.js b/server-console/src/main.js index 876f1fddaa..1a71e9b1d3 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -426,7 +426,7 @@ var labels = { } }, marketplace: { - label: 'Marketplace', + label: 'Market', click: function() { StartInterface("hifiapp:hifi/commerce/purchases/Purchases.qml"); pendingNotifications[HifiNotificationType.MARKETPLACE] = false; diff --git a/server-console/src/modules/hf-acctinfo.js b/server-console/src/modules/hf-acctinfo.js index 5e0c9f7f65..ba12e6a5ce 100644 --- a/server-console/src/modules/hf-acctinfo.js +++ b/server-console/src/modules/hf-acctinfo.js @@ -36,7 +36,10 @@ function AccountInfo() { AccountInfo.prototype = { accessToken: function(metaverseUrl) { - return this.data[metaverseUrl]["accessToken"]["token"]; + if(this.data && this.data[metaverseUrl] && this.data[metaverseUrl]["accessToken"]) { + return this.data[metaverseUrl]["accessToken"]["token"]; + } + return null; }, _parseUInt32: function () { if (!this.rawData || (this.rawData.length - this.parseOffset < 4)) { diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index e4f2d5b408..8fb262e9f0 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -9,14 +9,18 @@ const GetBuildInfo = hfApp.getBuildInfo; const buildInfo = GetBuildInfo(); const notificationIcon = path.join(__dirname, '../../resources/console-notification.png'); -const NOTIFICATION_POLL_TIME_MS = 15 * 1000; +const STORIES_NOTIFICATION_POLL_TIME_MS = 15 * 1000; +const PEOPLE_NOTIFICATION_POLL_TIME_MS = 15 * 1000; +const WALLET_NOTIFICATION_POLL_TIME_MS = 15 * 1000; +const MARKETPLACE_NOTIFICATION_POLL_TIME_MS = 15 * 1000; + const METAVERSE_SERVER_URL= process.env.HIFI_METAVERSE_URL ? process.env.HIFI_METAVERSE_URL : 'https://metaverse.highfidelity.com' const STORIES_URL= '/api/v1/user_stories'; const USERS_URL= '/api/v1/users'; const ECONOMIC_ACTIVITY_URL= '/api/v1/commerce/history'; const UPDATES_URL= '/api/v1/commerce/available_updates'; const MAX_NOTIFICATION_ITEMS=30 -const STARTUP_MAX_NOTIFICATION_ITEMS=5 +const STARTUP_MAX_NOTIFICATION_ITEMS=1 const StartInterface=hfApp.startInterface; @@ -43,61 +47,70 @@ HifiNotification.prototype = { switch(this.type) { case NotificationType.GOTO: if(typeof(this.data) == "number") { - text = this.data + " events are happening." + if (this.data == 1) { + text = "You have " + this.data + " event invitation pending." + } + else { + text = "You have " + this.data + " event invitations pending." + } message = "Click to open GOTO."; url="hifiapp:hifi/tablet/TabletAddressDialog.qml" } else { - text = this.data.username + " " + this.data.action_string + " in " + this.data.place_name; - message = "Click to go to " + this.data.place_name; + text = this.data.username + " " + this.data.action_string + " in " + this.data.place_name + "."; + message = "Click to go to " + this.data.place_name + "."; url = "hifi://" + this.data.place_name + this.data.path; } break; + case NotificationType.PEOPLE: if(typeof(this.data) == "number") { - text = this.data + " of your connections is online." + if (this.data == 1) { + text = this.data + " of your connections is online." + } + else { + text = this.data + " of your connections are online." + } message = "Click to open PEOPLE."; url="hifiapp:hifi/Pal.qml" } else { - console.log(this.data); - text = this.data.username + " is available in " + this.data.location.root.name + "!"; + text = this.data.username + " is available in " + this.data.location.root.name + "."; message = "Click to join them."; url="hifi://" + this.data.location.root.name + this.data.location.path; } break; + case NotificationType.WALLET: if(typeof(this.data) == "number") { - text = "You have " + this.data + " unread Wallet notifications!"; + if (this.data == 1) { + text = "You have " + this.data + " unread Wallet transaction."; + } + else { + text = "You have " + this.data + " unread Wallet transactions."; + } message = "Click to open WALLET." url = "hifiapp:hifi/commerce/wallet/Wallet.qml"; break; } - text = "Economic activity."; - var memo = ""; - if(this.data.sent_certs <= 0 && this.data.received_certs <= 0) { - if(this.data.received_money > 0) { - text = this.data.sender_name + " sent you " + this.data.received_money + " HFC!"; - memo = "memo: \"" + this.data.message + "\" "; - } - else { - return; - } - } - else { - text = this.data.message.replace(/<\/?[^>]+(>|$)/g, ""); - } - message = memo + "Click to open WALLET."; + text = this.data.message.replace(/<\/?[^>]+(>|$)/g, ""); + message = "Click to open WALLET."; url = "hifiapp:hifi/commerce/wallet/Wallet.qml"; break; + case NotificationType.MARKETPLACE: if(typeof(this.data) == "number") { - text = this.data + " of your purchased items have updates available!"; + if (this.data == 1) { + text = this.data + " of your purchased items has an update available."; + } + else { + text = this.data + " of your purchased items have updates available."; + } } else { - text = "There's an update available for your version of " + this.data.base_item_title + "!"; + text = "Update available for " + this.data.base_item_title + "."; } - message = "Click to open MARKETPLACE."; + message = "Click to open MARKET."; url = "hifiapp:hifi/commerce/purchases/Purchases.qml"; break; } @@ -113,11 +126,15 @@ HifiNotification.prototype = { } } -function HifiNotifications(config, callback) { +function HifiNotifications(config, menuNotificationCallback) { this.config = config; - this.callback = callback; + this.menuNotificationCallback = menuNotificationCallback; this.onlineUsers = new Set([]); - this.since = new Date(this.config.get("notifySince", "1970-01-01T00:00:00.000Z")); + this.storiesSince = new Date(this.config.get("storiesNotifySince", "1970-01-01T00:00:00.000Z")); + this.peopleSince = new Date(this.config.get("peopleNotifySince", "1970-01-01T00:00:00.000Z")); + this.walletSince = new Date(this.config.get("walletNotifySince", "1970-01-01T00:00:00.000Z")); + this.marketplaceSince = new Date(this.config.get("marketplaceNotifySince", "1970-01-01T00:00:00.000Z")); + this.enable(this.enabled()); notifier.on('click', function(notifierObject, options) { StartInterface(options.url); @@ -129,35 +146,59 @@ HifiNotifications.prototype = { this.config.set("enableTrayNotifications", enabled); if(enabled) { var _this = this; - this.pollTimer = setInterval(function() { - var acctInfo = new AccountInfo(); - var token = acctInfo.accessToken(METAVERSE_SERVER_URL); - var _since = _this.since; - _this.since = new Date(); - IsInterfaceRunning(function(running) { - if(running) { - return; - } - _this.pollForStories(_since, token); - _this.pollForConnections(_since, token); - _this.pollForEconomicActivity(_since, token); - _this.pollForMarketplaceUpdates(_since, token); - }); + this.storiesPollTimer = setInterval(function() { + var _since = _this.storiesSince; + _this.storiesSince = new Date(); + _this.pollForStories(_since); }, - NOTIFICATION_POLL_TIME_MS); + STORIES_NOTIFICATION_POLL_TIME_MS); + + this.peoplePollTimer = setInterval(function() { + var _since = _this.peopleSince; + _this.peopleSince = new Date(); + _this.pollForConnections(_since); + }, + PEOPLE_NOTIFICATION_POLL_TIME_MS); + + this.walletPollTimer = setInterval(function() { + var _since = _this.walletSince; + _this.walletSince = new Date(); + _this.pollForEconomicActivity(_since); + }, + WALLET_NOTIFICATION_POLL_TIME_MS); + + this.marketplacePollTimer = setInterval(function() { + var _since = _this.marketplaceSince; + _this.marketplaceSince = new Date(); + _this.pollForMarketplaceUpdates(_since); + }, + MARKETPLACE_NOTIFICATION_POLL_TIME_MS); } - else if(this.pollTimer) { - clearInterval(this.pollTimer); + else { + if(this.storiesTimer) { + clearInterval(this.storiesTimer); + } + if(this.peoplePollTimer) { + clearInterval(this.peoplePollTimer); + } + if(this.walletPollTimer) { + clearInterval(this.walletPollTimer); + } + if(this.marketplacePollTimer) { + clearInterval(this.marketplacePollTimer); + } } }, enabled: function() { return this.config.get("enableTrayNotifications", true); }, stopPolling: function() { - this.config.set("notifySince", this.since.toISOString()); - if(this.pollTimer) { - clearInterval(this.pollTimer); - } + this.config.set("storiesNotifySince", this.storiesSince.toISOString()); + this.config.set("peopleNotifySince", this.peopleSince.toISOString()); + this.config.set("walletNotifySince", this.walletSince.toISOString()); + this.config.set("marketplaceNotifySince", this.marketplaceSince.toISOString()); + + this.enable(false); }, _pollToDisableHighlight: function(notifyType, error, data) { if (error || !data.body) { @@ -172,58 +213,81 @@ HifiNotifications.prototype = { } console.log(content); if(!content.total_entries) { - this.callback(notifyType, false); + this.menuNotificationCallback(notifyType, false); } }, - _pollCommon: function(notifyType, error, data, since) { - var maxNotificationItemCount = since.getTime() ? MAX_NOTIFICATION_ITEMS : STARTUP_MAX_NOTIFICATION_ITEMS; - if (error || !data.body) { - console.log("Error: unable to get " + url); - return false; - } - var content = JSON.parse(data.body); - if(!content || content.status != 'success') { - console.log("Error: unable to get " + url); - return false; - } - console.log(content); - if(!content.total_entries) { - return; - } - this.callback(notifyType, true); - if(content.total_entries >= maxNotificationItemCount) { - var notification = new HifiNotification(notifyType, content.total_entries); - notification.show(); - } - else { - var notifyData = [] - switch(notifyType) { - case NotificationType.GOTO: - notifyData = content.user_stories; - break; - case NotificationType.PEOPLE: - notifyData = content.data.users; - break; - case NotificationType.WALLET: - notifyData = content.data.history; - break; - case NotificationType.MARKETPLACE: - notifyData = content.data.updates; - break; - } + _pollCommon: function(notifyType, url, since, finished) { - notifyData.forEach(function(notifyDataEntry) { - var notification = new HifiNotification(notifyType, notifyDataEntry); - notification.show(); - }); - } - }, - pollForStories: function(since, token) { var _this = this; - var _token = token; + IsInterfaceRunning(function(running) { + if(running) { + finished(false); + return; + } + var acctInfo = new AccountInfo(); + var token = acctInfo.accessToken(METAVERSE_SERVER_URL); + if(!token) { + return; + } + request.get({ + uri: url, + 'auth': { + 'bearer': token + } + }, function (error, data) { + + var maxNotificationItemCount = since.getTime() ? MAX_NOTIFICATION_ITEMS : STARTUP_MAX_NOTIFICATION_ITEMS; + if (error || !data.body) { + console.log("Error: unable to get " + url); + finished(false); + return; + } + var content = JSON.parse(data.body); + if(!content || content.status != 'success') { + console.log("Error: unable to get " + url); + finished(false); + return; + } + console.log(content); + if(!content.total_entries) { + finished(true, token); + return; + } + _this.menuNotificationCallback(notifyType, true); + if(content.total_entries >= maxNotificationItemCount) { + var notification = new HifiNotification(notifyType, content.total_entries); + notification.show(); + } + else { + var notifyData = [] + switch(notifyType) { + case NotificationType.GOTO: + notifyData = content.user_stories; + break; + case NotificationType.PEOPLE: + notifyData = content.data.users; + break; + case NotificationType.WALLET: + notifyData = content.data.history; + break; + case NotificationType.MARKETPLACE: + notifyData = content.data.updates; + break; + } + + notifyData.forEach(function(notifyDataEntry) { + var notification = new HifiNotification(notifyType, notifyDataEntry); + notification.show(); + }); + } + finished(true, token); + }); + }); + }, + pollForStories: function(since) { + var _this = this; var actions = 'announcement'; var options = [ - 'now=' + new Date().toISOString(), 'since=' + since.getTime() / 1000, 'include_actions=announcement', 'restriction=open,hifi', @@ -233,81 +297,105 @@ HifiNotifications.prototype = { console.log("Polling for stories"); var url = METAVERSE_SERVER_URL + STORIES_URL + '?' + options.join('&'); console.log(url); - request.get({ - uri: url, - 'auth': { - 'bearer': _token - } - }, function (error, data) { - _this._pollCommon(NotificationType.GOTO, error, data, since); + + _this._pollCommon(NotificationType.STORIES, + url, + since, + function (success, token) { + if(success) { + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=announcement', + 'restriction=open,hifi', + 'require_online=true', + 'per_page=1' + ]; + var url = METAVERSE_SERVER_URL + STORIES_URL + '?' + options.join('&'); + // call a second time to determine if there are no more stories and we should + // put out the light. + request.get({ + uri: url, + 'auth': { + 'bearer': token + } + }, function(error, data) { + _this._pollToDisableHighlight(NotificationType.GOTO, error, data); + }); + } + }); + }, + pollForConnections: function(since) { + var _this = this; + var _since = since; + IsInterfaceRunning(function(running) { + if(running) { + return; + } var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=announcement', - 'restriction=open,hifi', - 'require_online=true', - 'per_page=1' - ]; - var url = METAVERSE_SERVER_URL + STORIES_URL + '?' + options.join('&'); + 'filter=connections', + 'status=online', + 'page=1', + 'per_page=' + MAX_NOTIFICATION_ITEMS + ]; + console.log("Polling for connections"); + var url = METAVERSE_SERVER_URL + USERS_URL + '?' + options.join('&'); + console.log(url); + var acctInfo = new AccountInfo(); + var token = acctInfo.accessToken(METAVERSE_SERVER_URL); + if(!token) { + return; + } request.get({ uri: url, 'auth': { - 'bearer': _token - } - }, function(error, data) { - _this._pollToDisableHighlight(NotificationType.GOTO, error, data); - }); - }); - }, - pollForConnections: function(since, token) { - var _this = this; - var _since = since; - var options = [ - 'filter=connections', - 'status=online', - 'page=1', - 'per_page=' + MAX_NOTIFICATION_ITEMS - ]; - console.log("Polling for connections"); - var url = METAVERSE_SERVER_URL + USERS_URL + '?' + options.join('&'); - console.log(url); - request.get({ - uri: url, - 'auth': { - 'bearer': token - } - }, function (error, data) { - // Users is a special case as we keep track of online users locally. - var maxNotificationItemCount = since.getTime() ? MAX_NOTIFICATION_ITEMS : STARTUP_MAX_NOTIFICATION_ITEMS; - if (error || !data.body) { - console.log("Error: unable to get " + url); - return false; - } - var content = JSON.parse(data.body); - if(!content || content.status != 'success') { - console.log("Error: unable to get " + url); - return false; - } - console.log(content); - if(!content.total_entries) { - _this.callback(NotificationType.PEOPLE, false); - _this.onlineUsers = new Set([]); - return; - } - - var currentUsers = new Set([]); - content.data.users.forEach(function(user) { - currentUsers.add(user.username); - if(!_this.onlineUsers.has(user.username)) { - _this.callback(NotificationType.PEOPLE, true); - _this.onlineUsers.add(user.username); - var notification = new HifiNotification(NotificationType.PEOPLE, user); - notification.show(); + 'bearer': token } + }, function (error, data) { + // Users is a special case as we keep track of online users locally. + var maxNotificationItemCount = _since.getTime() ? MAX_NOTIFICATION_ITEMS : STARTUP_MAX_NOTIFICATION_ITEMS; + if (error || !data.body) { + console.log("Error: unable to get " + url); + return false; + } + var content = JSON.parse(data.body); + if(!content || content.status != 'success') { + console.log("Error: unable to get " + url); + return false; + } + console.log(content); + if(!content.total_entries) { + _this.menuNotificationCallback(NotificationType.PEOPLE, false); + _this.onlineUsers = new Set([]); + return; + } + + var currentUsers = new Set([]); + var newUsers = new Set([]); + content.data.users.forEach(function(user) { + currentUsers.add(user.username); + if(!_this.onlineUsers.has(user.username)) { + newUsers.add(user); + _this.onlineUsers.add(user.username); + } + }); + _this.onlineUsers = currentUsers; + if(newUsers.size) { + _this.menuNotificationCallback(NotificationType.PEOPLE, true); + } + + if(newUsers.size >= maxNotificationItemCount) { + var notification = new HifiNotification(NotificationType.PEOPLE, newUsers.size); + notification.show(); + return; + } + newUsers.forEach(function(user) { + var notification = new HifiNotification(NotificationType.PEOPLE, user); + notification.show(); + }); }); - _this.onlineUsers = currentUsers; }); }, - pollForEconomicActivity: function(since, token) { + pollForEconomicActivity: function(since) { var _this = this; var options = [ 'since=' + since.getTime() / 1000, @@ -319,16 +407,9 @@ HifiNotifications.prototype = { console.log("Polling for economic activity"); var url = METAVERSE_SERVER_URL + ECONOMIC_ACTIVITY_URL + '?' + options.join('&'); console.log(url); - request.post({ - uri: url, - 'auth': { - 'bearer': token - } - }, function (error, data) { - _this._pollCommon(NotificationType.WALLET, error, data, since); - }); + _this._pollCommon(NotificationType.WALLET, url, since, function () {}); }, - pollForMarketplaceUpdates: function(since, token) { + pollForMarketplaceUpdates: function(since) { var _this = this; var options = [ 'since=' + since.getTime() / 1000, @@ -338,13 +419,21 @@ HifiNotifications.prototype = { console.log("Polling for marketplace update"); var url = METAVERSE_SERVER_URL + UPDATES_URL + '?' + options.join('&'); console.log(url); - request.put({ - uri: url, - 'auth': { - 'bearer': token - } - }, function (error, data) { - _this._pollCommon(NotificationType.MARKETPLACE, error, data, since); + _this._pollCommon(NotificationType.MARKETPLACE, url, since, function (success, token) { + if(success) { + var options = [ + 'page=1', + 'per_page=1' + ]; + request.get({ + uri: url, + 'auth': { + 'bearer': token + } + }, function(error, data) { + _this._pollToDisableHighlight(NotificationType.MARKETPLACE, error, data); + }); + } }); } };