Bug 757261: Apps in the Cloud Manager and Service; r=mconnor

This commit is contained in:
Anant Narayanan 2012-06-02 23:32:37 -07:00
parent 19204af86d
commit 5a1ed35be7
9 changed files with 878 additions and 52 deletions

View File

@ -438,6 +438,7 @@
@BINPATH@/components/SyncComponents.manifest
@BINPATH@/components/AitcComponents.manifest
@BINPATH@/components/Weave.js
@BINPATH@/components/Aitc.js
#endif
@BINPATH@/components/TelemetryPing.js
@BINPATH@/components/TelemetryPing.manifest
@ -512,6 +513,7 @@
@BINPATH@/@PREF_DIR@/firefox-branding.js
#ifdef MOZ_SERVICES_SYNC
@BINPATH@/@PREF_DIR@/services-sync.js
@BINPATH@/@PREF_DIR@/services-aitc.js
#endif
@BINPATH@/greprefs.js
@BINPATH@/defaults/autoconfig/platform.js

121
services/aitc/Aitc.js Normal file
View File

@ -0,0 +1,121 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://services-common/utils.js");
function AitcService() {
this.aitc = null;
this.wrappedJSObject = this;
}
AitcService.prototype = {
classID: Components.ID("{a3d387ca-fd26-44ca-93be-adb5fda5a78d}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsINavHistoryObserver,
Ci.nsISupportsWeakReference]),
observe: function observe(subject, topic, data) {
switch (topic) {
case "app-startup":
// We listen for this event beacause Aitc won't work until there is
// atleast 1 visible top-level XUL window.
Services.obs.addObserver(this, "sessionstore-windows-restored", true);
break;
case "sessionstore-windows-restored":
Services.obs.removeObserver(this, "sessionstore-windows-restored");
// Don't start AITC if classic sync is on.
Cu.import("resource://services-common/preferences.js");
if (Preferences.get("services.sync.engine.apps", false)) {
return;
}
// Start AITC only if it is enabled.
if (!Preferences.get("services.aitc.enabled", false)) {
return;
}
// Start AITC service if apps.enabled is true. If false, we look
// in the browser history to determine if they're an "apps user". If
// an entry wasn't found, we'll watch for navigation to either the
// marketplace or dashboard and switch ourselves on then.
if (Preferences.get("apps.enabled", false)) {
this.start();
return;
}
// Set commonly used URLs.
this.DASHBOARD_URL = CommonUtils.makeURI(
Preferences.get("services.aitc.dashboard.url")
);
this.MARKETPLACE_URL = CommonUtils.makeURI(
Preferences.get("services.aitc.marketplace.url")
);
if (this.hasUsedApps()) {
Preferences.set("apps.enabled", true);
this.start();
return;
}
// Wait and see if the user wants anything apps related.
PlacesUtils.history.addObserver(this, true);
break;
}
},
start: function start() {
Cu.import("resource://services-aitc/main.js");
if (!this.aitc) {
this.aitc = new Aitc();
}
},
hasUsedApps: function hasUsedApps() {
// There is no easy way to determine whether a user is "using apps".
// The best we can do right now is to see if they have visited either
// the Mozilla dashboard or Marketplace. See bug 760898.
let gh = PlacesUtils.ghistory2;
if (gh.isVisited(this.DASHBOARD_URL)) {
return true;
}
if (gh.isVisited(this.MARKETPLACE_URL)) {
return true;
}
return false;
},
// nsINavHistoryObserver. We are only interested in onVisit().
onBeforeDeleteURI: function() {},
onBeginUpdateBatch: function() {},
onClearHistory: function() {},
onDeleteURI: function() {},
onDeleteVisits: function() {},
onEndUpdateBatch: function() {},
onPageChanged: function() {},
onPageExpired: function() {},
onTitleChanged: function() {},
onVisit: function onVisit(uri) {
if (!uri.equals(this.MARKETPLACE_URL) && !uri.equals(this.DASHBOARD_URL)) {
return;
}
PlacesUtils.history.removeObserver(this);
Preferences.set("apps.enabled", true);
this.start();
return;
},
};
const components = [AitcService];
const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

View File

@ -1,5 +1,5 @@
# service.js
component {a3d387ca-fd26-44ca-93be-adb5fda5a78d} service.js
# Aitc.js
component {a3d387ca-fd26-44ca-93be-adb5fda5a78d} Aitc.js
contract @mozilla.org/services/aitc;1 {a3d387ca-fd26-44ca-93be-adb5fda5a78d}
category app-startup AitcService service,@mozilla.org/services/aitc;1
# Register resource aliases

View File

@ -11,7 +11,7 @@ include $(DEPTH)/config/autoconf.mk
EXTRA_COMPONENTS = \
AitcComponents.manifest \
service.js \
Aitc.js \
$(NULL)
PREF_JS_EXPORTS = $(srcdir)/services-aitc.js

View File

@ -0,0 +1,161 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EXPORTED_SYMBOLS = ["Aitc"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-aitc/manager.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/log4moz.js");
Cu.import("resource://services-common/preferences.js");
function Aitc() {
this._log = Log4Moz.repository.getLogger("Service.AITC");
this._log.level = Log4Moz.Level[Preferences.get(
"services.aitc.service.log.level"
)];
this._log.info("Loading AitC");
this.DASHBOARD_ORIGIN = CommonUtils.makeURI(
Preferences.get("services.aitc.dashboard.url")
).prePath;
this._manager = new AitcManager(this._init.bind(this));
}
Aitc.prototype = {
// The goal of the init function is to be ready to activate the AITC
// client whenever the user is looking at the dashboard.
_init: function init() {
let self = this;
// This is called iff the user is currently looking the dashboard.
function dashboardLoaded(browser) {
let win = browser.contentWindow;
self._log.info("Dashboard was accessed " + win);
// If page is ready to go, fire immediately.
if (win.document && win.document.readyState == "complete") {
self._manager.userActive(win);
return;
}
// Only fire event after the page fully loads.
browser.contentWindow.addEventListener(
"DOMContentLoaded",
function _contentLoaded(event) {
self._manager.userActive(win);
},
false
);
}
// This is called when the user's attention is elsewhere.
function dashboardUnloaded() {
self._log.info("Dashboard closed or in background");
self._manager.userIdle();
}
// Called when a URI is loaded in any tab. We have to listen for this
// because tabSelected is not called if I open a new tab which loads
// about:home and then navigate to the dashboard, or navigation via
// links on the currently open tab.
let listener = {
onLocationChange: function onLocationChange(browser, pr, req, loc, flag) {
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (win.gBrowser.selectedBrowser == browser) {
if (loc.prePath == self.DASHBOARD_ORIGIN) {
dashboardLoaded(browser);
}
}
}
};
// Called when the current tab selection changes.
function tabSelected(event) {
let browser = event.target.linkedBrowser;
if (browser.currentURI.prePath == self.DASHBOARD_ORIGIN) {
dashboardLoaded(browser);
} else {
dashboardUnloaded();
}
}
// Add listeners for all windows opened in the future.
function winWatcher(subject, topic) {
if (topic != "domwindowopened") {
return;
}
subject.addEventListener("load", function winWatcherLoad() {
subject.removeEventListener("load", winWatcherLoad, false);
let doc = subject.document.documentElement;
if (doc.getAttribute("windowtype") == "navigator:browser") {
let browser = subject.gBrowser;
browser.addTabsProgressListener(listener);
browser.tabContainer.addEventListener("TabSelect", tabSelected);
}
}, false);
}
Services.ww.registerNotification(winWatcher);
// Add listeners for all current open windows.
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
let browser = enumerator.getNext().gBrowser;
browser.addTabsProgressListener(listener);
browser.tabContainer.addEventListener("TabSelect", tabSelected);
// Also check the currently open URI.
if (browser.currentURI.prePath == this.DASHBOARD_ORIGIN) {
dashboardLoaded(browser);
}
}
// Add listeners for app installs/uninstall.
Services.obs.addObserver(this, "webapps-sync-install", false);
Services.obs.addObserver(this, "webapps-sync-uninstall", false);
// Add listener for idle service.
let idleSvc = Cc["@mozilla.org/widget/idleservice;1"].
getService(Ci.nsIIdleService);
idleSvc.addIdleObserver(this,
Preferences.get("services.aitc.main.idleTime"));
},
observe: function(subject, topic, data) {
let app;
switch (topic) {
case "webapps-sync-install":
app = JSON.parse(data);
this._log.info(app.origin + " was installed, initiating PUT");
this._manager.appEvent("install", app);
break;
case "webapps-sync-uninstall":
app = JSON.parse(data);
this._log.info(app.origin + " was uninstalled, initiating PUT");
this._manager.appEvent("uninstall", app);
break;
case "idle":
this._log.info("User went idle");
if (this._manager) {
this._manager.userIdle();
}
break;
case "back":
this._log.info("User is no longer idle");
let win = Services.wm.getMostRecentWindow("navigator:browser");
if (win && win.gBrowser.currentURI.prePath == this.DASHBOARD_ORIGIN &&
this._manager) {
this._manager.userActive();
}
break;
}
},
};

View File

@ -0,0 +1,573 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EXPORTED_SYMBOLS = ["AitcManager"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://services-aitc/client.js");
Cu.import("resource://services-aitc/browserid.js");
Cu.import("resource://services-aitc/storage.js");
Cu.import("resource://services-common/log4moz.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://services-common/tokenserverclient.js");
Cu.import("resource://services-common/utils.js");
const PREFS = new Preferences("services.aitc.");
const TOKEN_TIMEOUT = 240000; // 4 minutes
const DASHBOARD_URL = PREFS.get("dashboard.url");
const MARKETPLACE_URL = PREFS.get("marketplace.url");
/**
* The constructor for the manager takes a callback, which will be invoked when
* the manager is ready (construction is asynchronous). *DO NOT* call any
* methods on this object until the callback has been invoked, doing so will
* lead to undefined behaviour.
*/
function AitcManager(cb) {
this._client = null;
this._getTimer = null;
this._putTimer = null;
this._lastToken = 0;
this._lastEmail = null;
this._dashboardWindow = null;
this._log = Log4Moz.repository.getLogger("Service.AITC.Manager");
this._log.level = Log4Moz.Level[Preferences.get("manager.log.level")];
this._log.info("Loading AitC manager module");
// Check if we have pending PUTs from last time.
let self = this;
this._pending = new AitcQueue("webapps-pending.json", function _queueDone() {
// Inform the AitC service that we're good to go!
self._log.info("AitC manager has finished loading");
try {
cb(true);
} catch (e) {
self._log.error(new Error("AitC manager callback threw " + e));
}
// Schedule them, but only if we can get a silent assertion.
self._makeClient(function(err, client) {
if (!err && client) {
self._client = client;
self._processQueue();
}
}, false);
});
}
AitcManager.prototype = {
/**
* State of the user. ACTIVE implies user is looking at the dashboard,
* PASSIVE means either not at the dashboard or the idle timer started.
*/
_ACTIVE: 1,
_PASSIVE: 2,
/**
* Smart setter that will only call _setPoll is the value changes.
*/
_clientState: null,
get _state() {
return this._clientState;
},
set _state(value) {
if (this._clientState == value) {
return;
}
this._clientState = value;
this._setPoll();
},
/**
* Local app was just installed or uninstalled, ask client to PUT if user
* is logged in.
*/
appEvent: function appEvent(type, app) {
// Add this to the equeue.
let self = this;
let obj = {type: type, app: app, retries: 0, lastTime: 0};
this._pending.enqueue(obj, function _enqueued(err, rec) {
if (err) {
self._log.error("Could not add " + type + " " + app + " to queue");
return;
}
// If we already have a client (i.e. user is logged in), attempt to PUT.
if (self._client) {
self._processQueue();
return;
}
// If not, try a silent client creation.
self._makeClient(function(err, client) {
if (!err && client) {
self._client = client;
self._processQueue();
}
// If user is not logged in, we'll just have to try later.
});
});
},
/**
* User is looking at dashboard. Start polling actively, but if user isn't
* logged in, prompt for them to login via a dialog.
*/
userActive: function userActive(win) {
// Stash a reference to the dashboard window in case we need to prompt
this._dashboardWindow = win;
if (this._client) {
this._state = this._ACTIVE;
return;
}
// Make client will first try silent login, if it doesn't work, a popup
// will be shown in the context of the dashboard. We shouldn't be
// trying to make a client every time this function is called, there is
// room for optimization (Bug 750607).
let self = this;
this._makeClient(function(err, client) {
if (err) {
// Notify user of error (Bug 750610).
self._log.error("Client not created at Dashboard");
return;
}
self._client = client;
self._state = self._ACTIVE;
}, true, win);
},
/**
* User is idle, (either by idle observer, or by not being on the dashboard).
* When the user is no longer idle and the dashboard is the current active
* page, a call to userActive MUST be made.
*/
userIdle: function userIdle() {
this._state = this._PASSIVE;
this._dashboardWindow = null;
},
/**
* Poll the AITC server for any changes and process them. It is safe to call
* this function multiple times. Last caller wins. The function will
* grab the current user state from _state and act accordingly.
*
* Invalid states will cause this function to throw.
*/
_setPoll: function _setPoll() {
if (this._state == this._ACTIVE && !this._client) {
throw new Error("_setPoll(ACTIVE) called without client");
}
if (this._state != this._ACTIVE && this._state != this._PASSIVE) {
throw new Error("_state is invalid " + this._state);
}
if (!this._client) {
// User is not logged in, we cannot do anything.
self._log.warn("_setPoll called but user not logged in, ignoring");
return;
}
// Check if there are any PUTs pending first.
if (this._pending.length && !(this._putTimer)) {
// There are pending PUTs and no timer, so let's process them. GETs will
// resume after the PUTs finish (see processQueue)
this._processQueue();
return;
}
// Do one GET soon, but only if user is active.
let getFreq;
if (this._state == this._ACTIVE) {
CommonUtils.nextTick(this._checkServer, this);
getFreq = PREFS.get("manager.getActiveFreq");
} else {
getFreq = PREFS.get("manager.getPassiveFreq");
}
// Cancel existing timer, if any.
if (this._getTimer) {
this._getTimer.cancel();
this._getTimer = null;
}
// Start the timer for GETs.
let self = this;
this._log.info("Starting GET timer");
this._getTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._getTimer.initWithCallback({notify: this._checkServer.bind(this)},
getFreq, Ci.nsITimer.TYPE_REPEATING_SLACK);
this._log.info("GET timer set, next attempt in " + getFreq + "ms");
},
/**
* Checks if the current token we hold is valid. If not, we obtain a new one
* and execute the provided func. If a token could not be obtained, func will
* not be called and an error will be logged.
*/
_validateToken: function _validateToken(func) {
if (Date.now() - this._lastToken < TOKEN_TIMEOUT) {
func();
return;
}
let win;
if (this._state == this.ACTIVE) {
win = this._dashboardWindow;
}
let self = this;
this._refreshToken(function(err, done) {
if (!done) {
this._log.warn("_checkServer could not refresh token, aborting");
return;
}
func();
}, win);
},
/**
* Do a GET check on the server to see if we have any new apps. Abort if
* there are pending PUTs. If we GET some apps, send to storage for
* further processing.
*/
_checkServer: function _checkServer() {
if (!this._client) {
throw new Error("_checkServer called without a client");
}
if (this._pending.length) {
this._log.warn("_checkServer aborted because of pending PUTs");
return;
}
this._validateToken(this._getApps.bind(this));
},
_getApps: function _getApps() {
// Do a GET
this._log.info("Attempting to getApps");
let self = this;
this._client.getApps(function gotApps(err, apps) {
if (err) {
// Error was logged in client.
return;
}
if (!apps) {
// No changes, got 304.
return;
}
if (!apps.length) {
// Empty array, nothing to process
self._log.info("No apps found on remote server");
return;
}
// Send list of remote apps to storage to apply locally
AitcStorage.processApps(apps, function processedApps() {
self._log.info("processApps completed successfully, changes applied");
});
});
},
/**
* Go through list of apps to PUT and attempt each one. If we fail, try
* again in PUT_FREQ. Will throw if called with an empty, _reschedule()
* makes sure that we don't.
*/
_processQueue: function _processQueue() {
if (!this._client) {
throw new Error("_processQueue called without a client");
}
if (!this._pending.length) {
throw new Error("_processQueue called with an empty queue");
}
if (this._putInProgress) {
// The network request sent out as a result to the last call to
// _processQueue still isn't done. A timer is created they all
// finish to make sure this function is called again if neccessary.
return;
}
this._validateToken(this._putApps.bind(this));
},
_putApps: function _putApps() {
this._putInProgress = true;
let record = this._pending.peek();
this._log.info("Processing record type " + record.type);
let self = this;
function _clientCallback(err, done) {
// Send to end of queue if unsuccessful or err.removeFromQueue is false.
if (err && !err.removeFromQueue) {
self._log.info("PUT failed, re-adding to queue");
// Update retries and time
record.retries += 1;
record.lastTime = new Date().getTime();
// Add updated record to the end of the queue.
self._pending.enqueue(record, function(err, done) {
if (err) {
self._log.error("Enqueue failed " + err);
_reschedule();
return;
}
// If record was successfully added, remove old record.
self._pending.dequeue(function(err, done) {
if (err) {
self._log.error("Dequeue failed " + err);
}
_reschedule();
return;
});
});
}
// If succeeded or client told us to remove from queue
self._log.info("_putApp asked us to remove it from queue");
self._pending.dequeue(function(err, done) {
if (err) {
self._log.error("Dequeue failed " + e);
}
_reschedule();
});
}
function _reschedule() {
// Release PUT lock
self._putInProgress = false;
// We just finished PUTting an object, try the next one immediately,
// but only if haven't tried it already in the last putFreq (ms).
if (!self._pending.length) {
// Start GET timer now that we're done with PUTs.
self._setPoll();
return;
}
let obj = self._pending.peek();
let cTime = new Date().getTime();
let freq = PREFS.get("manager.putFreq");
// We tried this object recently, we'll come back to it later.
if (obj.lastTime && ((cTime - obj.lastTime) < freq)) {
self._log.info("Scheduling next processQueue in " + freq);
CommonUtils.namedTimer(self._processQueue, freq, self, "_putTimer");
return;
}
// Haven't tried this PUT yet, do it immediately.
self._log.info("Queue non-empty, processing next PUT");
self._processQueue();
}
switch (record.type) {
case "install":
this._client.remoteInstall(record.app, _clientCallback);
break;
case "uninstall":
record.app.deleted = true;
this._client.remoteUninstall(record.app, _clientCallback);
break;
default:
this._log.warn(
"Unrecognized type " + record.type + " in queue, removing"
);
let self = this;
this._pending.dequeue(function _dequeued(err) {
if (err) {
self._log.error("Dequeue of unrecognized app type failed");
}
_reschedule();
});
}
},
/* Obtain a (new) token from the Sagrada token server. If win is is specified,
* the user will be asked to login via UI, if required. The callback's
* signature is cb(err, done). If a token is obtained successfully, done will
* be true and err will be null.
*/
_refreshToken: function _refreshToken(cb, win) {
if (!this._client) {
throw new Error("_refreshToken called without an active client");
}
this._log.info("Token refresh requested");
let self = this;
function refreshedAssertion(err, assertion) {
if (!err) {
self._getToken(assertion, function(err, token) {
if (err) {
cb(err, null);
return;
}
self._lastToken = Date.now();
self._client.updateToken(token);
cb(null, true);
});
return;
}
// Silent refresh was asked for.
if (!win) {
cb(err, null);
return;
}
// Prompt user to login.
self._makeClient(function(err, client) {
if (err) {
cb(err, null);
return;
}
// makeClient sets an updated token.
self._client = client;
cb(null, true);
}, win);
}
let options = { audience: DASHBOARD_URL };
if (this._lastEmail) {
options.requiredEmail = this._lastEmail;
} else {
options.sameEmailAs = MARKETPLACE_URL;
}
BrowserID.getAssertion(refreshedAssertion, options);
},
/* Obtain a token from Sagrada token server, given a BrowserID assertion
* cb(err, token) will be invoked on success or failure.
*/
_getToken: function _getToken(assertion, cb) {
let url = PREFS.get("tokenServer.url") + "/1.0/aitc/1.0";
let client = new TokenServerClient();
this._log.info("Obtaining token from " + url);
let self = this;
try {
client.getTokenFromBrowserIDAssertion(url, assertion, function(err, tok) {
self._gotToken(err, tok, cb);
});
} catch (e) {
cb(new Error(e), null);
}
},
// Token recieved from _getToken.
_gotToken: function _gotToken(err, tok, cb) {
if (!err) {
this._log.info("Got token from server: " + JSON.stringify(tok));
cb(null, tok);
return;
}
let msg = err.name + " in _getToken: " + err.error;
this._log.error(msg);
cb(msg, null);
},
// Extract the email address from a BrowserID assertion.
_extractEmail: function _extractEmail(assertion) {
// Please look the other way while I do this. Thanks.
let chain = assertion.split("~");
let len = chain.length;
if (len < 2) {
return null;
}
try {
// We need CommonUtils.decodeBase64URL.
let cert = JSON.parse(atob(
chain[0].split(".")[1].replace("-", "+", "g").replace("_", "/", "g")
));
return cert.principal.email;
} catch (e) {
return null;
}
},
/* To start the AitcClient we need a token, for which we need a BrowserID
* assertion. If login is true, makeClient will ask the user to login in
* the context of win. cb is called with (err, client).
*/
_makeClient: function makeClient(cb, login, win) {
if (!cb) {
throw new Error("makeClient called without callback");
}
if (login && !win) {
throw new Error("makeClient called with login as true but no win");
}
let self = this;
let ctxWin = win;
function processAssertion(val) {
// Store the email we got the token for so we can refresh.
self._lastEmail = self._extractEmail(val);
self._log.info("Got assertion from BrowserID, creating token");
self._getToken(val, function(err, token) {
if (err) {
cb(err, null);
return;
}
// Store when we got the token so we can refresh it as needed.
self._lastToken = Date.now();
// We only create one client instance, store values in a pref tree
cb(null, new AitcClient(
token, new Preferences("services.aitc.client.")
));
});
}
function gotSilentAssertion(err, val) {
self._log.info("gotSilentAssertion called");
if (err) {
// If we were asked to let the user login, do the popup method.
if (login) {
self._log.info("Could not obtain silent assertion, retrying login");
BrowserID.getAssertionWithLogin(function gotAssertion(err, val) {
if (err) {
self._log.error(err);
cb(err, false);
return;
}
processAssertion(val);
}, ctxWin);
return;
}
self._log.warn("Could not obtain assertion in _makeClient");
cb(err, false);
} else {
processAssertion(val);
}
}
// Check if we can get assertion silently first
self._log.info("Attempting to obtain assertion silently")
BrowserID.getAssertion(gotSilentAssertion, {
audience: DASHBOARD_URL,
sameEmailAs: MARKETPLACE_URL
});
},
};

View File

@ -1,47 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function AitcService() {
this.wrappedJSObject = this;
}
AitcService.prototype = {
classID: Components.ID("{a3d387ca-fd26-44ca-93be-adb5fda5a78d}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function observe(subject, topic, data) {
switch (topic) {
case "app-startup":
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.addObserver(this, "final-ui-startup", true);
break;
case "final-ui-startup":
// Start AITC service after 2000ms, only if classic sync is off.
Cu.import("resource://services-common/preferences.js");
if (Preferences.get("services.sync.engine.apps", false)) {
return;
}
if (!Preferences.get("services.aitc.enabled", true)) {
return;
}
Cu.import("resource://services-common/utils.js");
CommonUtils.namedTimer(function() {
// Kick-off later!
}, 2000, this, "timer");
break;
}
}
};
const components = [AitcService];
const NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

View File

@ -2,8 +2,22 @@ pref("services.aitc.browserid.url", "https://browserid.org/sign_in");
pref("services.aitc.browserid.log.level", "Debug");
pref("services.aitc.client.log.level", "Debug");
pref("services.aitc.storage.log.level", "Debug");
pref("services.aitc.client.timeout", 120); // 120 seconds
pref("services.aitc.client.timeout", 120);
pref("services.aitc.dashboard.url", "https://myapps.mozillalabs.com");
pref("services.aitc.main.idleTime", 120000); // 2 minutes
pref("services.aitc.manager.putFreq", 10000); // 10 seconds
pref("services.aitc.manager.getActiveFreq", 120000); // 2 minutes
pref("services.aitc.manager.getPassiveFreq", 7200000); // 2 hours
pref("services.aitc.manager.log.level", "Debug");
pref("services.aitc.marketplace.url", "https://marketplace.mozilla.org");
pref("services.aitc.service.log.level", "Debug");
// Temporary value. Change to the production server when we get the OK from server ops
pref("services.aitc.tokenServer.url", "https://stage-token.services.mozilla.com");
pref("services.aitc.storage.log.level", "Debug");

View File

@ -1,6 +1,8 @@
const modules = [
"client.js",
"browserid.js",
"main.js",
"manager.js",
"storage.js"
];