diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46cebc1661..26c244a9d3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3427,7 +3427,13 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); QString addressLookupString; if (urlIndex != -1) { - addressLookupString = arguments().value(urlIndex + 1); + QUrl url(arguments().value(urlIndex + 1)); + if (url.scheme() == URL_SCHEME_HIFIAPP) { + QmlCommerce commerce; + commerce.openSystemApp(url.path()); + } else { + addressLookupString = arguments().value(urlIndex + 1); + } } static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location"; @@ -7674,6 +7680,9 @@ void Application::openUrl(const QUrl& url) const { if (!url.isEmpty()) { if (url.scheme() == URL_SCHEME_HIFI) { DependencyManager::get()->handleLookupString(url.toString()); + } else if (url.scheme() == URL_SCHEME_HIFIAPP) { + QmlCommerce commerce; + commerce.openSystemApp(url.path()); } else { // address manager did not handle - ask QDesktopServices to handle QDesktopServices::openUrl(url); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 06da18148f..e1f9f63bfb 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -47,6 +47,16 @@ QmlCommerce::QmlCommerce() { _appsPath = PathUtils::getAppDataPath() + "Apps/"; } +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); +} + + void QmlCommerce::getWalletStatus() { auto wallet = DependencyManager::get(); wallet->getWalletStatus(); @@ -353,7 +363,7 @@ bool QmlCommerce::openApp(const QString& itemHref) { // Read from the file to know what .html or .qml document to open QFile appFile(_appsPath + "/" + appHref.fileName()); if (!appFile.open(QIODevice::ReadOnly)) { - qCDebug(commerce) << "Couldn't open local .app.json file."; + qCDebug(commerce) << "Couldn't open local .app.json file:" << _appsPath << "/" << appHref.fileName(); return false; } QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 79d8e82e71..bee30e1b62 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -24,6 +24,7 @@ class QmlCommerce : public QObject { public: QmlCommerce(); + void openSystemApp(const QString& appPath); signals: void walletStatusResult(uint walletStatus); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 3e3c9da148..d9396ae4d1 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -176,7 +176,7 @@ int main(int argc, const char* argv[]) { if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) { if (parser.isSet(urlOption)) { QUrl url = QUrl(parser.value(urlOption)); - if (url.isValid() && url.scheme() == URL_SCHEME_HIFI) { + if (url.isValid() && (url.scheme() == URL_SCHEME_HIFI || url.scheme() == URL_SCHEME_HIFIAPP)) { qDebug() << "Writing URL to local socket"; socket.write(url.toString().toUtf8()); if (!socket.waitForBytesWritten(5000)) { diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 31ff6da873..839e269fd4 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -32,6 +32,7 @@ namespace NetworkingConstants { const QString URL_SCHEME_ABOUT = "about"; const QString URL_SCHEME_HIFI = "hifi"; +const QString URL_SCHEME_HIFIAPP = "hifiapp"; const QString URL_SCHEME_QRC = "qrc"; const QString URL_SCHEME_FILE = "file"; const QString URL_SCHEME_HTTP = "http"; diff --git a/server-console/src/main.js b/server-console/src/main.js index 0fd2659fe9..876f1fddaa 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -41,6 +41,11 @@ const ProcessGroupStates = hfprocess.ProcessGroupStates; const hfApp = require('./modules/hf-app.js'); const GetBuildInfo = hfApp.getBuildInfo; const StartInterface = hfApp.startInterface; +const getRootHifiDataDirectory = hfApp.getRootHifiDataDirectory; +const getDomainServerClientResourcesDirectory = hfApp.getDomainServerClientResourcesDirectory; +const getAssignmentClientResourcesDirectory = hfApp.getAssignmentClientResourcesDirectory; +const getApplicationDataDirectory = hfApp.getApplicationDataDirectory; + const osType = os.type(); @@ -55,32 +60,7 @@ const HOME_CONTENT_URL = "http://cdn.highfidelity.com/content-sets/home-tutorial const buildInfo = GetBuildInfo(); -function getRootHifiDataDirectory(local) { - var organization = buildInfo.organization; - if (osType == 'Windows_NT') { - if (local) { - return path.resolve(osHomeDir(), 'AppData/Local', organization); - } else { - return path.resolve(osHomeDir(), 'AppData/Roaming', organization); - } - } else if (osType == 'Darwin') { - return path.resolve(osHomeDir(), 'Library/Application Support', organization); - } else { - return path.resolve(osHomeDir(), '.local/share/', organization); - } -} -function getDomainServerClientResourcesDirectory() { - return path.join(getRootHifiDataDirectory(), '/domain-server'); -} - -function getAssignmentClientResourcesDirectory() { - return path.join(getRootHifiDataDirectory(), '/assignment-client'); -} - -function getApplicationDataDirectory(local) { - return path.join(getRootHifiDataDirectory(local), '/Server Console'); -} // Update lock filepath const UPDATER_LOCK_FILENAME = ".updating"; @@ -352,8 +332,8 @@ const HifiNotifications = hfNotifications.HifiNotifications; const HifiNotificationType = hfNotifications.NotificationType; var pendingNotifications = {} -function notificationCallback(notificationType) { - pendingNotifications[notificationType] = true; +function notificationCallback(notificationType, pending = true) { + pendingNotifications[notificationType] = pending; updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } @@ -424,7 +404,7 @@ var labels = { goto: { label: 'Goto', click: function() { - StartInterface(""); + StartInterface("hifiapp:hifi/tablet/TabletAddressDialog.qml"); pendingNotifications[HifiNotificationType.GOTO] = false; updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } @@ -432,7 +412,7 @@ var labels = { people: { label: 'People', click: function() { - StartInterface(""); + StartInterface("hifiapp:hifi/Pal.qml"); pendingNotifications[HifiNotificationType.PEOPLE] = false; updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } @@ -440,7 +420,7 @@ var labels = { wallet: { label: 'Wallet', click: function() { - StartInterface(""); + StartInterface("hifiapp:hifi/commerce/wallet/Wallet.qml"); pendingNotifications[HifiNotificationType.WALLET] = false; updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } @@ -448,7 +428,7 @@ var labels = { marketplace: { label: 'Marketplace', click: function() { - StartInterface(""); + StartInterface("hifiapp:hifi/commerce/purchases/Purchases.qml"); pendingNotifications[HifiNotificationType.MARKETPLACE] = false; updateTrayMenu(homeServer ? homeServer.state : ProcessGroupStates.STOPPED); } diff --git a/server-console/src/modules/hf-acctinfo.js b/server-console/src/modules/hf-acctinfo.js new file mode 100644 index 0000000000..5e0c9f7f65 --- /dev/null +++ b/server-console/src/modules/hf-acctinfo.js @@ -0,0 +1,135 @@ +'use strict' + +const request = require('request'); +const extend = require('extend'); +const util = require('util'); +const events = require('events'); +const childProcess = require('child_process'); +const fs = require('fs-extra'); +const os = require('os'); +const path = require('path'); + +const hfApp = require('./hf-app.js'); +const getInterfaceDataDirectory = hfApp.getInterfaceDataDirectory; + + +const VariantTypes = { + USER_TYPE: 1024 +} + +function AccountInfo() { + + var accountInfoPath = path.join(getInterfaceDataDirectory(), '/AccountInfo.bin'); + this.rawData = null; + this.parseOffset = 0; + try { + this.rawData = fs.readFileSync(accountInfoPath); + + this.data = this._parseMap(); + + } catch(e) { + console.log(e); + log.debug("AccountInfo file not found: " + accountInfoPath); + } +} + +AccountInfo.prototype = { + + accessToken: function(metaverseUrl) { + return this.data[metaverseUrl]["accessToken"]["token"]; + }, + _parseUInt32: function () { + if (!this.rawData || (this.rawData.length - this.parseOffset < 4)) { + throw "Expected uint32"; + } + var result = this.rawData.readUInt32BE(this.parseOffset); + this.parseOffset += 4; + return result; + }, + _parseMap: function() { + var result = {}; + var n = this._parseUInt32(); + for(var i = 0; i < n; i++) { + var key = this._parseQString(); + result[key] = this._parseVariant(); + } + return result; + }, + _parseVariant: function() { + var varType = this._parseUInt32(); + var isNull = this.rawData[this.parseOffset++]; + + switch(varType) { + case VariantTypes.USER_TYPE: + //user type + var userTypeName = this._parseByteArray().toString('ascii').slice(0,-1); + if(userTypeName == "DataServerAccountInfo") { + return this._parseDataServerAccountInfo(); + } + else { + throw "Unknown custom type " + userTypeName; + } + break; + } + }, + _parseByteArray: function() { + var length = this._parseUInt32(); + if (length == 0xffffffff) { + return null; + } + var result = this.rawData.slice(this.parseOffset, this.parseOffset+length); + this.parseOffset += length; + return result; + + }, + _parseQString: function() { + if (!this.rawData || (this.rawData.length <= this.parseOffset)) { + throw "Expected QString"; + } + // length in bytes; + var length = this._parseUInt32(); + if(length == 0xFFFFFFFF) { + return null; + } + + if(this.rawData.length - this.parseOffset < length) { + throw "Insufficient buffer length for QString parsing"; + } + + // Convert from BE UTF16 to LE + var resultBuffer = this.rawData.slice(this.parseOffset, this.parseOffset+length); + resultBuffer.swap16(); + var result = resultBuffer.toString('utf16le'); + this.parseOffset += length; + return result; + }, + _parseDataServerAccountInfo: function() { + return { + accessToken: this._parseOAuthAccessToken(), + username: this._parseQString(), + xmppPassword: this._parseQString(), + discourseApiKey: this._parseQString(), + walletId: this._parseUUID(), + privateKey: this._parseByteArray(), + domainId: this._parseUUID(), + tempDomainId: this._parseUUID(), + tempDomainApiKey: this._parseQString() + + } + }, + _parseOAuthAccessToken: function() { + return { + token: this._parseQString(), + timestampHigh: this._parseUInt32(), + timestampLow: this._parseUInt32(), + tokenType: this._parseQString(), + refreshToken: this._parseQString() + } + }, + _parseUUID: function() { + this.parseOffset += 16; + return null; + } +} + +exports.AccountInfo = AccountInfo; \ No newline at end of file diff --git a/server-console/src/modules/hf-app.js b/server-console/src/modules/hf-app.js index 28b97f582a..625715b392 100644 --- a/server-console/src/modules/hf-app.js +++ b/server-console/src/modules/hf-app.js @@ -6,6 +6,7 @@ const pathFinder = require('./path-finder'); const path = require('path'); const argv = require('yargs').argv; const hfprocess = require('./hf-process'); +const osHomeDir = require('os-homedir'); const Process = hfprocess.Process; const binaryType = argv.binaryType; @@ -69,3 +70,35 @@ exports.isInterfaceRunning = function(done) { var pInterface = new Process('interface', 'interface.exe'); return pInterface.isRunning(done); } + + +exports.getRootHifiDataDirectory = function(local) { + var organization = buildInfo.organization; + if (osType == 'Windows_NT') { + if (local) { + return path.resolve(osHomeDir(), 'AppData/Local', organization); + } else { + return path.resolve(osHomeDir(), 'AppData/Roaming', organization); + } + } else if (osType == 'Darwin') { + return path.resolve(osHomeDir(), 'Library/Application Support', organization); + } else { + return path.resolve(osHomeDir(), '.local/share/', organization); + } +} + +exports.getDomainServerClientResourcesDirectory = function() { + return path.join(exports.getRootHifiDataDirectory(), '/domain-server'); +} + +exports.getAssignmentClientResourcesDirectory = function() { + return path.join(exports.getRootHifiDataDirectory(), '/assignment-client'); +} + +exports.getApplicationDataDirectory = function(local) { + return path.join(exports.getRootHifiDataDirectory(local), '/Server Console'); +} + +exports.getInterfaceDataDirectory = function(local) { + return path.join(exports.getRootHifiDataDirectory(local), '/Interface'); +} \ No newline at end of file diff --git a/server-console/src/modules/hf-appinfo.js b/server-console/src/modules/hf-appinfo.js deleted file mode 100644 index 9fac0ca61c..0000000000 --- a/server-console/src/modules/hf-appinfo.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -const request = require('request'); -const extend = require('extend'); -const util = require('util'); -const events = require('events'); -const childProcess = require('child_process'); -const fs = require('fs-extra'); -const os = require('os'); -const path = require('path'); diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index 06be365208..785ecc4ec0 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -4,14 +4,18 @@ const os = require('os'); const process = require('process'); const hfApp = require('./hf-app'); const path = require('path'); +const AccountInfo = require('./hf-acctinfo').AccountInfo; +const GetBuildInfo = hfApp.getBuildInfo; +const buildInfo = GetBuildInfo(); const notificationIcon = path.join(__dirname, '../../resources/console-notification.png'); const NOTIFICATION_POLL_TIME_MS = 15 * 1000; -const METAVERSE_SERVER_URL= process.env.HIFI_METAVERSE_URL ? process.env.HIFI_METAVERSE_URL : 'https://highfidelity.com' +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 StartInterface=hfApp.startInterface; @@ -31,55 +35,60 @@ function HifiNotification(notificationType, notificationData) { HifiNotification.prototype = { show: function() { + var text = ""; + var message = ""; + var url = null; + var app = null; switch(this.type) { case NotificationType.GOTO: - var text = this.data.username + " " + this.data.action_string + " in " + this.data.place_name; - notifier.notify({ - notificationType: this.type, - icon: notificationIcon, - title: text, - message: "Click to goto " + this.data.place_name, - wait: true, - url: "hifi://" + this.data.place_name + this.data.path - }); + 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: - var text = this.data.username + " has logged in."; - notifier.notify({ - notificationType: this.type, - icon: notificationIcon, - title: text, - message: "Click to join them in " + this.data.location.root.name, - wait: true, - url: "hifi://" + this.data.location.root.name + this.data.location.path - }); + 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: - var text = "Economic activity."; - notifier.notify({ - notificationType: this.type, - icon: notificationIcon, - title: text, - message: "Click to open your wallet", - wait: true, - app: "Wallet" - }); + if(typeof(this.data) == "number") { + text = "You have " + this.data + " unread Wallet notifications!"; + message = "Click to open your 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 your wallet."; + url = "hifiapp:hifi/commerce/wallet/Wallet.qml"; break; - case NotificationType.MARKETPLACE: - var text = "One of your marketplace items has an update."; - notifier.notify({ - notificationType: this.type, - icon: notificationIcon, - title: text, - message: "Click to start the marketplace app", - wait: true, - app: "Marketplace" - }); + text = "There's an update available for your version of " + this.data.base_item_title + "!"; + message = "Click to open the marketplace."; + url = "hifiapp:hifi/commerce/purchases/Purchases.qml"; break; } + notifier.notify({ + notificationType: this.type, + icon: notificationIcon, + title: text, + message: message, + wait: true, + appID: buildInfo.appUserModelId, + url: url + }); } } @@ -89,7 +98,6 @@ function HifiNotifications(config, callback) { this.since = new Date(this.config.get("notifySince", "1970-01-01T00:00:00.000Z")); this.enable(this.enabled()); notifier.on('click', function(notifierObject, options) { - console.log(options); StartInterface(options.url); }); } @@ -100,16 +108,18 @@ HifiNotifications.prototype = { 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, _this.callback); - _this.pollForConnections(_since, _this.callback); - _this.pollForEconomicActivity(_since, _this.callback); - _this.pollForMarketplaceUpdates(_since, _this.callback); + _this.pollForStories(_since, token); + _this.pollForConnections(_since, token); + _this.pollForEconomicActivity(_since, token); + _this.pollForMarketplaceUpdates(_since, token); }); }, NOTIFICATION_POLL_TIME_MS); @@ -127,134 +137,128 @@ HifiNotifications.prototype = { clearInterval(this.pollTimer); } }, - pollForStories: function(since, callback) { + _pollCommon: function(notifyType, error, data, since) { + var maxNotificationItemCount = (since.getTime() == 0) ? MAX_NOTIFICATION_ITEMS : 1; + 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; + } + + notifyData.forEach(function(data) { + var notification = new HifiNotification(notifyType, data); + notification.show(); + }); + } + }, + pollForStories: function(since, token) { var _this = this; var actions = 'announcement'; var options = [ 'now=' + new Date().toISOString(), - 'since=' + since.toISOString(), + 'since=' + since.getTime() / 1000, 'include_actions=announcement', 'restriction=open,hifi', - 'require_online=true' + 'require_online=true', + 'per_page='+MAX_NOTIFICATION_ITEMS ]; console.log("Polling for stories"); var url = METAVERSE_SERVER_URL + STORIES_URL + '?' + options.join('&'); + console.log(url); request({ uri: url }, function (error, data) { - if (error || !data.body) { - console.log("Error: unable to get " + url); - return; - } - var content = JSON.parse(data.body); - if(!content || content.status != 'success') { - console.log("Error: unable to get " + url); - return; - } - content.user_stories.forEach(function(story) { - var updated_at = new Date(story.updated_at); - if (updated_at < since) { - return; - } - callback(NotificationType.GOTO); - var notification = new HifiNotification(NotificationType.GOTO, story); - notification.show(); - }); + _this._pollCommon(NotificationType.GOTO, error, data, since); }); }, - pollForConnections: function(since, callback) { + pollForConnections: function(since, token) { var _this = this; var options = [ 'filter=connections', - 'since=' + since.toISOString(), - 'status=online' + 'since=' + since.getTime() / 1000, + 'status=online', + 'page=1', + 'per_page=' + MAX_NOTIFICATION_ITEMS ]; console.log("Polling for connections"); var url = METAVERSE_SERVER_URL + USERS_URL + '?' + options.join('&'); - request({ - uri: url + console.log(url); + request.get({ + uri: url, + 'auth': { + 'bearer': token + } }, function (error, data) { - if (error || !data.body) { - console.log("Error: unable to get " + url); - return; - } - var content = JSON.parse(data.body); - if(!content || content.status != 'success') { - console.log("Error: unable to get " + url); - return; - } - console.log(content.data); - content.data.users.forEach(function(user) { - if(user.online) { - callback(NotificationType.PEOPLE); - var notification = new HifiNotification(NotificationType.PEOPLE, user); - notification.show(); - } - }); + _this._pollCommon(NotificationType.PEOPLE, error, data, since); }); }, - pollForEconomicActivity: function(since, callback) { + pollForEconomicActivity: function(since, token) { var _this = this; var options = [ - 'filter=connections', - 'since=' + since.toISOString(), - 'status=online' + 'since=' + since.getTime() / 1000, + 'page=1', + 'per_page=' + 1000 // total_entries is incorrect for wallet queries if results + // aren't all on one page, so grab them all on a single page + // for now. ]; console.log("Polling for economic activity"); var url = METAVERSE_SERVER_URL + ECONOMIC_ACTIVITY_URL + '?' + options.join('&'); + console.log(url); request.post({ - uri: url + uri: url, + 'auth': { + 'bearer': token + } }, function (error, data) { - if (error || !data.body) { - console.log("Error " + error + ": unable to post " + url); - console.log(data); - return; - } - var content = JSON.parse(data.body); - if(!content || content.status != 'success') { - console.log(data.body); - console.log("Error " + content.status + ": unable to post " + url); - return; - } - console.log(content.data); - content.data.users.forEach(function(user) { - if(user.online) { - callback(NotificationType.PEOPLE); - var notification = new HifiNotification(NotificationType.PEOPLE, user); - notification.show(); - } - }); + _this._pollCommon(NotificationType.WALLET, error, data, since); }); }, - pollForMarketplaceUpdates: function(since, callback) { + pollForMarketplaceUpdates: function(since, token) { var _this = this; var options = [ - 'filter=connections', - 'since=' + since.toISOString(), - 'status=online' + 'since=' + since.getTime() / 1000, + 'page=1', + 'per_page=' + MAX_NOTIFICATION_ITEMS ]; console.log("Polling for marketplace update"); var url = METAVERSE_SERVER_URL + UPDATES_URL + '?' + options.join('&'); + console.log(url); request.put({ - uri: url + uri: url, + 'auth': { + 'bearer': token + } }, function (error, data) { - if (error || !data.body) { - console.log("Error " + error + ": unable to put " + url); - return; - } - var content = JSON.parse(data.body); - if(!content || content.status != 'success') { - console.log(data.body); - console.log("Error " + content.status + ": unable to put " + url); - return; - } - content.data.users.forEach(function(user) { - if(user.online) { - callback(NotificationType.PEOPLE); - var notification = new HifiNotification(NotificationType.PEOPLE, user); - notification.show(); - } - }); + _this._pollCommon(NotificationType.MARKETPLACE, error, data, since); }); } };