Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
19
gaia/apps/findmydevice/build/build.js
Executable file
19
gaia/apps/findmydevice/build/build.js
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
var utils = require('utils');
|
||||
|
||||
exports.execute = function(config) {
|
||||
utils.copyToStage(config);
|
||||
var init = utils.getFile(config.STAGE_APP_DIR, 'js', 'config.js');
|
||||
var content = {
|
||||
'api_url': 'https://find.firefox.com',
|
||||
'api_version': '1',
|
||||
};
|
||||
|
||||
var distDir = config.GAIA_DISTRIBUTION_DIR;
|
||||
utils.writeContent(init, 'Config = ' +
|
||||
utils.getDistributionFileContent('findmydevice', content, distDir) +
|
||||
';');
|
||||
};
|
||||
23
gaia/apps/findmydevice/index.html
Executable file
23
gaia/apps/findmydevice/index.html
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Shared code -->
|
||||
<script src="shared/js/dump.js"></script>
|
||||
<script src="shared/js/passcode_helper.js"></script>
|
||||
<script defer src="shared/js/settings_listener.js"></script>
|
||||
<script defer src="shared/js/settings_helper.js"></script>
|
||||
<script defer src="shared/js/async_storage.js"></script>
|
||||
<script defer src="shared/js/settings_url.js"></script>
|
||||
<script defer src="shared/js/findmydevice_iac_api.js"></script>
|
||||
|
||||
<script defer src="js/lib/hawk.js"></script>
|
||||
<script defer src="js/config.js"></script>
|
||||
<script defer src="js/requester.js"></script>
|
||||
<script defer src="js/commands.js"></script>
|
||||
<script defer src="js/findmydevice.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
220
gaia/apps/findmydevice/js/commands.js
Executable file
220
gaia/apps/findmydevice/js/commands.js
Executable file
|
|
@ -0,0 +1,220 @@
|
|||
/* global DUMP, SettingsHelper, SettingsListener, SettingsURL, FindMyDevice,
|
||||
PasscodeHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
var Commands = {
|
||||
TRACK_UPDATE_INTERVAL_MS: 10000,
|
||||
|
||||
_ringer: null,
|
||||
|
||||
_lockscreenEnabled: false,
|
||||
|
||||
_lockscreenPassCodeEnabled: false,
|
||||
|
||||
_geolocationEnabled: false,
|
||||
|
||||
init: function fmdc_init() {
|
||||
var ringer = this._ringer = new Audio();
|
||||
ringer.mozAudioChannelType = 'ringer';
|
||||
ringer.loop = true;
|
||||
|
||||
var ringtoneURL = new SettingsURL();
|
||||
SettingsListener.observe('dialer.ringtone', '', function(value) {
|
||||
var ringing = !ringer.paused;
|
||||
|
||||
ringer.pause();
|
||||
ringer.src = ringtoneURL.set(value);
|
||||
if (ringing) {
|
||||
ringer.play();
|
||||
}
|
||||
});
|
||||
|
||||
var self = this;
|
||||
SettingsListener.observe('lockscreen.enabled', false, function(value) {
|
||||
self._lockscreenEnabled = value;
|
||||
});
|
||||
|
||||
SettingsListener.observe('lockscreen.passcode-lock.enabled', false,
|
||||
function(value) {
|
||||
self._lockscreenPassCodeEnabled = value;
|
||||
}
|
||||
);
|
||||
|
||||
SettingsListener.observe('geolocation.enabled', false, function(value) {
|
||||
self._geolocationEnabled = value;
|
||||
});
|
||||
},
|
||||
|
||||
getEnabledCommands: function fmdc_get_enabled_commands() {
|
||||
var commands = Object.keys(this._commands);
|
||||
if (!this._geolocationEnabled) {
|
||||
var idx = commands.indexOf('track');
|
||||
if (idx >= 0) {
|
||||
commands.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return commands;
|
||||
},
|
||||
|
||||
invokeCommand: function fmdc_get_command(name, args) {
|
||||
FindMyDevice.beginHighPriority('command');
|
||||
this._commands[name].apply(this, args);
|
||||
},
|
||||
|
||||
deviceHasPasscode: function fmdc_device_has_passcode() {
|
||||
return !!(this._lockscreenEnabled && this._lockscreenPassCodeEnabled);
|
||||
},
|
||||
|
||||
_watchPositionId: null,
|
||||
|
||||
_trackTimeoutId: null,
|
||||
|
||||
_ringTimeoutId: null,
|
||||
|
||||
_commands: {
|
||||
track: function fmdc_track(duration, reply) {
|
||||
var self = this;
|
||||
|
||||
function stop() {
|
||||
navigator.geolocation.clearWatch(self._watchPositionId);
|
||||
self._watchPositionId = null;
|
||||
clearTimeout(self._trackTimeoutId);
|
||||
self._trackTimeoutId = null;
|
||||
SettingsHelper('findmydevice.tracking').set(false);
|
||||
FindMyDevice.endHighPriority('command');
|
||||
}
|
||||
|
||||
if (this._watchPositionId !== null || this._trackTimeoutId !== null) {
|
||||
// already tracking
|
||||
stop();
|
||||
}
|
||||
|
||||
if (duration === 0) {
|
||||
if (reply) {
|
||||
reply(true);
|
||||
}
|
||||
FindMyDevice.endHighPriority('command');
|
||||
return;
|
||||
}
|
||||
|
||||
var lastPositionTimestamp = 0;
|
||||
|
||||
// start watching the current position, but throttle updates to one
|
||||
// every TRACK_UPDATE_INTERVAL_MS
|
||||
SettingsHelper('findmydevice.tracking').set(true);
|
||||
self._watchPositionId = navigator.geolocation.watchPosition(
|
||||
function(position) {
|
||||
DUMP('received location (' +
|
||||
position.coords.latitude + ', ' +
|
||||
position.coords.longitude + ')'
|
||||
);
|
||||
|
||||
var timeElapsed = position.timestamp - lastPositionTimestamp;
|
||||
if (timeElapsed < self.TRACK_UPDATE_INTERVAL_MS) {
|
||||
DUMP('ignoring position due to throttling');
|
||||
return;
|
||||
}
|
||||
|
||||
lastPositionTimestamp = position.timestamp;
|
||||
reply(true, position);
|
||||
}, function(error) {
|
||||
reply(false, 'failed to get location: ' + error.message);
|
||||
});
|
||||
|
||||
duration = (isNaN(duration) || duration < 0) ? 1 : duration;
|
||||
self._trackTimeoutId = setTimeout(stop, duration * 1000);
|
||||
},
|
||||
|
||||
erase: function fmdc_erase(reply) {
|
||||
navigator.mozPower.factoryReset('wipe');
|
||||
|
||||
// factoryReset() won't return, unless we're testing,
|
||||
// in which case mozPower is a mock. The reply() below
|
||||
// is thus only used for testing.
|
||||
reply(true);
|
||||
},
|
||||
|
||||
killswitch: function fmdc_killswitch(reply) {
|
||||
if (navigator.mozKillSwitch) {
|
||||
navigator.mozKillSwitch.enable();
|
||||
}
|
||||
|
||||
FindMyDevice.endHighPriority('command');
|
||||
reply(true);
|
||||
},
|
||||
|
||||
lock: function fmdc_lock(message, passcode, reply) {
|
||||
var pr;
|
||||
var settings = {
|
||||
'lockscreen.enabled': true,
|
||||
'lockscreen.notifications-preview.enabled': false,
|
||||
'lockscreen.passcode-lock.enabled': true,
|
||||
'lockscreen.lock-immediately': true
|
||||
};
|
||||
|
||||
if (message) {
|
||||
settings['lockscreen.lock-message'] = message;
|
||||
}
|
||||
|
||||
if (passcode) {
|
||||
pr = PasscodeHelper.set(passcode);
|
||||
}
|
||||
|
||||
var request = SettingsListener.getSettingsLock().set(settings);
|
||||
|
||||
Promise.all([pr, request]).then(() => {
|
||||
reply(true);
|
||||
}).catch(() => {
|
||||
reply(false);
|
||||
});
|
||||
|
||||
FindMyDevice.endHighPriority('command');
|
||||
},
|
||||
|
||||
ring: function fmdc_ring(duration, reply) {
|
||||
var ringer = this._ringer;
|
||||
|
||||
var stop = function() {
|
||||
ringer.pause();
|
||||
ringer.currentTime = 0;
|
||||
clearTimeout(this._ringTimeoutId);
|
||||
this._ringTimeoutId = null;
|
||||
FindMyDevice.endHighPriority('command');
|
||||
}.bind(this);
|
||||
|
||||
var ringing = !ringer.paused || this._ringTimeoutId !== null;
|
||||
if (ringing || duration === 0) {
|
||||
if (ringing && duration === 0) {
|
||||
stop();
|
||||
}
|
||||
|
||||
if (reply) {
|
||||
reply(true);
|
||||
}
|
||||
FindMyDevice.endHighPriority('command');
|
||||
return;
|
||||
}
|
||||
|
||||
var request = SettingsListener.getSettingsLock().set({
|
||||
// hard-coded max volume taken from
|
||||
// https://wiki.mozilla.org/WebAPI/AudioChannels
|
||||
'audio.volume.notification': 15
|
||||
});
|
||||
|
||||
request.onsuccess = function() {
|
||||
ringer.play();
|
||||
reply(true);
|
||||
};
|
||||
|
||||
request.onerror = function() {
|
||||
reply(false, 'failed to set volume');
|
||||
};
|
||||
|
||||
this._ringTimeoutId = setTimeout(stop, duration * 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Commands.init();
|
||||
665
gaia/apps/findmydevice/js/findmydevice.js
Executable file
665
gaia/apps/findmydevice/js/findmydevice.js
Executable file
|
|
@ -0,0 +1,665 @@
|
|||
/* global SettingsHelper */
|
||||
/* global asyncStorage */
|
||||
/* global Requester */
|
||||
/* global Commands */
|
||||
/* global Config */
|
||||
/* global DUMP */
|
||||
/* global IAC_API_WAKEUP_REASON_ENABLED_CHANGED */
|
||||
/* global IAC_API_WAKEUP_REASON_LOGIN */
|
||||
/* global IAC_API_WAKEUP_REASON_LOGOUT */
|
||||
/* global IAC_API_WAKEUP_REASON_STALE_REGISTRATION */
|
||||
/* global IAC_API_WAKEUP_REASON_TRY_DISABLE */
|
||||
/* global IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED */
|
||||
|
||||
'use strict';
|
||||
|
||||
var FindMyDevice = {
|
||||
_state: null,
|
||||
|
||||
_registering: false,
|
||||
|
||||
_refreshingClientID: false,
|
||||
|
||||
_reply: {},
|
||||
|
||||
_registered: false,
|
||||
|
||||
_registeredHelper: null,
|
||||
|
||||
_enabled: false,
|
||||
|
||||
_enabledHelper: null,
|
||||
|
||||
_loggedIn: false,
|
||||
|
||||
_currentClientID: '',
|
||||
|
||||
_currentClientIDHelper: null,
|
||||
|
||||
_canDisable: false,
|
||||
|
||||
_canDisableHelper: null,
|
||||
|
||||
_retryCount: -1,
|
||||
|
||||
_retryCountHelper: null,
|
||||
|
||||
_disableAttempt: false,
|
||||
|
||||
// There are two situations in which we want to make sure
|
||||
// FMD is alive:
|
||||
// - when it is running client logic that accesses the network;
|
||||
// - when it is executing a command with a duration.
|
||||
// Command wakelocks are managed by individual commands.
|
||||
// For client logic wakelocks, we use the fact that when a network
|
||||
// request succeeds, we'll either update 'findmydevice.registered',
|
||||
// or 'findmydevice.can-disable', or process a command and set
|
||||
// a 'ping' timer, and when it fails, we'll set a timer to retry.
|
||||
// Since each network transaction finishes with either a change
|
||||
// in one of these settings or with a timer, we release the locks
|
||||
// in the observers for the settings and in _scheduleAlarm.
|
||||
_highPriorityWakeLocks: {
|
||||
clientLogic: [],
|
||||
command: []
|
||||
},
|
||||
|
||||
_fxaReady: false,
|
||||
|
||||
init: function fmd_init() {
|
||||
var self = this;
|
||||
|
||||
navigator.mozId.watch({
|
||||
wantIssuer: 'firefox-accounts',
|
||||
audience: Config.api_url,
|
||||
onready: self._onReady.bind(self),
|
||||
onlogin: self._onLogin.bind(self),
|
||||
onlogout: self._onLogout.bind(self),
|
||||
onerror: self._onFxAError.bind(self)
|
||||
});
|
||||
},
|
||||
|
||||
_observeSettings: function fmd_observe_settings() {
|
||||
var settings = navigator.mozSettings;
|
||||
|
||||
settings.addObserver('findmydevice.registered',
|
||||
this._onRegisteredChanged.bind(this));
|
||||
settings.addObserver('findmydevice.enabled',
|
||||
this._onEnabledChanged.bind(this));
|
||||
settings.addObserver('findmydevice.current-clientid',
|
||||
this._onClientIDChanged.bind(this));
|
||||
|
||||
// We only allow disabling Find My Device if the same person
|
||||
// who first enabled it is logged in to FxA, and in that case
|
||||
// 'findmydevice.can-disable' is true.
|
||||
// However, note that FMD doesn't store the user's email address,
|
||||
// but rather a client ID that is derived from the FxA assertion
|
||||
// and stored in this._state.clientid.
|
||||
settings.addObserver('findmydevice.can-disable',
|
||||
this._onCanDisableChanged.bind(this));
|
||||
},
|
||||
|
||||
_onReady: function fmd_fxa_onready() {
|
||||
var self = this;
|
||||
this._loadState(function() {
|
||||
self._initSettings(self._initMessageHandlers.bind(self));
|
||||
});
|
||||
|
||||
this._fxaReady = true;
|
||||
this._observeSettings();
|
||||
},
|
||||
|
||||
_loadState: function fmd_load_state(callback) {
|
||||
var self = this;
|
||||
asyncStorage.getItem('findmydevice-state', function(state) {
|
||||
self._state = state;
|
||||
if (state) {
|
||||
Requester.setHawkCredentials(state.deviceid, state.secret);
|
||||
}
|
||||
|
||||
callback && callback();
|
||||
});
|
||||
},
|
||||
|
||||
_initSettings: function fmd_init_settings(callback) {
|
||||
// for each setting we're interested in, create a SettingsHelper,
|
||||
// cache its initial value in a property (e.g., _enabled), and
|
||||
// store the SettingsHelper as another property (e.g., _enabledHelper).
|
||||
var settingsToProperties = {
|
||||
// setting name : property name for cached value
|
||||
'findmydevice.enabled': '_enabled',
|
||||
'findmydevice.registered': '_registered',
|
||||
'findmydevice.current-clientid': '_currentClientID',
|
||||
'findmydevice.can-disable': '_canDisable',
|
||||
'findmydevice.retry-count': '_retryCount'
|
||||
};
|
||||
var settings = Object.keys(settingsToProperties);
|
||||
|
||||
var loaded = 0;
|
||||
var totalSettings = Object.keys(settingsToProperties).length;
|
||||
settings.forEach(function(s) {
|
||||
var prop = settingsToProperties[s];
|
||||
var helper = this[prop + 'Helper'] = SettingsHelper(s);
|
||||
helper.get((function(value) {
|
||||
this[prop] = value;
|
||||
if (++loaded === totalSettings) {
|
||||
callback && callback();
|
||||
}
|
||||
}).bind(this));
|
||||
}, this);
|
||||
},
|
||||
|
||||
_initMessageHandlers: function fmd_init_message_handlers() {
|
||||
navigator.mozSetMessageHandler('push', (function(message) {
|
||||
DUMP('findmydevice got push notification!');
|
||||
this._contactServer();
|
||||
}).bind(this));
|
||||
|
||||
navigator.mozSetMessageHandler('push-register', (function(message) {
|
||||
DUMP('findmydevice lost push endpoint, re-registering');
|
||||
this.beginHighPriority('clientLogic');
|
||||
this._registeredHelper.set(false);
|
||||
}).bind(this));
|
||||
|
||||
navigator.mozSetMessageHandler('alarm', (function(alarm) {
|
||||
DUMP('findmydevice alarm!');
|
||||
this._contactServer();
|
||||
this._refreshClientIDIfRegistered(false);
|
||||
}).bind(this));
|
||||
|
||||
navigator.mozSetMessageHandler('connection', (function(request) {
|
||||
var port = request.port;
|
||||
port.onmessage = (function(event) {
|
||||
if (request.keyword === 'findmydevice-wakeup') {
|
||||
DUMP('got wake up request');
|
||||
|
||||
var reason = event.data;
|
||||
if (reason === IAC_API_WAKEUP_REASON_ENABLED_CHANGED) {
|
||||
DUMP('enabled state changed, trying to reach the server');
|
||||
// Ensure the retry counter is reset to 0 on enable
|
||||
this._retryCountHelper.set(0);
|
||||
this._contactServer();
|
||||
} else if (reason === IAC_API_WAKEUP_REASON_STALE_REGISTRATION) {
|
||||
DUMP('stale registration, re-registering');
|
||||
this.beginHighPriority('clientLogic');
|
||||
this._registeredHelper.set(false);
|
||||
} else if (reason === IAC_API_WAKEUP_REASON_LOGIN) {
|
||||
DUMP('new login, invalidating client id');
|
||||
this.beginHighPriority('clientLogic');
|
||||
this._loggedIn = true;
|
||||
this._currentClientIDHelper.set('');
|
||||
} else if (reason === IAC_API_WAKEUP_REASON_LOGOUT) {
|
||||
DUMP('logout, invalidating client id');
|
||||
this.beginHighPriority('clientLogic');
|
||||
this._loggedIn = false;
|
||||
this._currentClientIDHelper.set('');
|
||||
} else if (reason === IAC_API_WAKEUP_REASON_TRY_DISABLE) {
|
||||
DUMP('refreshing client id and attempting to disable');
|
||||
this._disableAttempt = true;
|
||||
this._refreshClientIDIfRegistered(true);
|
||||
} else if (reason === IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED) {
|
||||
DUMP('unlocked');
|
||||
this._onLockscreenClosed();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.keyword === 'findmydevice-test') {
|
||||
DUMP('got request for test command!');
|
||||
this._processCommands(event.data, true);
|
||||
}
|
||||
}).bind(this);
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_contactServer: function fmd_contact_server() {
|
||||
this.beginHighPriority('clientLogic');
|
||||
if (this._registered && this._enabled) {
|
||||
this._replyAndFetchCommands();
|
||||
} else if (this._registered && !this._enabled) {
|
||||
this._reportDisabled();
|
||||
} else if (!this._registered && this._enabled) {
|
||||
this._register();
|
||||
} else {
|
||||
// XXX(ggp) this should never happen, but let's play
|
||||
// it safe and release lock we acquired
|
||||
DUMP('can\'t contact the server while not registered and not enabled!!');
|
||||
this.endHighPriority('clientLogic');
|
||||
}
|
||||
},
|
||||
|
||||
_reportDisabled: function fmd_report_disabled() {
|
||||
DUMP('reporting disabled');
|
||||
Requester.post(
|
||||
this._getCommandEndpoint(),
|
||||
{enabled: false},
|
||||
this._handleServerResponse.bind(this),
|
||||
this._handleServerError.bind(this));
|
||||
},
|
||||
|
||||
_register: function fmd_register() {
|
||||
DUMP('findmydevice attempting registration.');
|
||||
DUMP('registering: ' + this._registering);
|
||||
|
||||
if (this._registering) {
|
||||
this._registering = false;
|
||||
this.endHighPriority('clientLogic');
|
||||
return;
|
||||
}
|
||||
|
||||
this._registering = true;
|
||||
|
||||
if (this._state === null) {
|
||||
// We are not registered yet, so we need to give the server a FxA
|
||||
// assertion. Request a fresh one here and continue the registration
|
||||
// process from the login callback.
|
||||
|
||||
if (!this._loggedIn) {
|
||||
// Can't request() while not logged in, as that would bring up the
|
||||
// FxA dialog. Attempting a first registration while logged out is
|
||||
// theoretically possible (if, for example, a first registration
|
||||
// failed, and this is an alarm-driven re-attempt, and the user logged
|
||||
// out while we slept before this attempt), but it's a very unlikely
|
||||
// corner case. For now, just give up if this happens, but we still
|
||||
// need to notify the user somehow (bug 1013423).
|
||||
this._registering = false;
|
||||
this.endHighPriority('clientLogic');
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.mozId.request();
|
||||
} else {
|
||||
// We don't need to send assertions on re-registrations, just rely on
|
||||
// our client ID and HAWK signature to authenticate us. This works even
|
||||
// if we're logged out of FxA, so use null for the assertion.
|
||||
this._continueRegistration(null);
|
||||
}
|
||||
},
|
||||
|
||||
_onLogin: function fmd_on_login(assertion) {
|
||||
DUMP('logged in to FxA');
|
||||
|
||||
// XXX(ggp) When initializing, we'll process FxA's automatic invocation
|
||||
// of onlogin/onlogout before any IAC message, so use these calls to
|
||||
// initialize _loggedIn. However, when FMD is already running, there's
|
||||
// no guaranteee that onlogin/onlogout will fire before the IAC handler,
|
||||
// so we have dedicated IAC messages (IAC_API_WAKEUP_REASON_LOGIN and
|
||||
// IAC_API_WAKEUP_REASON_LOGOUT) to cover login state changes, and we
|
||||
// use the handlers for these messages to update _loggedIn.
|
||||
this._loggedIn = true;
|
||||
|
||||
if (this._refreshingClientID) {
|
||||
DUMP('resuming client id refresh');
|
||||
this._fetchClientID(assertion);
|
||||
}
|
||||
|
||||
if (!this._enabled || !this._registering) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are in the middle of a registration, so continue here
|
||||
this._continueRegistration(assertion);
|
||||
},
|
||||
|
||||
_continueRegistration: function fmd_continue_registration(assertion) {
|
||||
var self = this;
|
||||
|
||||
if (assertion == null && this._state == null) {
|
||||
// this shouldn't happen
|
||||
throw new Error('Trying to register with no assertion and no state!');
|
||||
}
|
||||
|
||||
var pushRequest = navigator.push.register();
|
||||
pushRequest.onsuccess = function fmd_push_handler() {
|
||||
DUMP('findmydevice received push endpoint!');
|
||||
|
||||
var endpoint = pushRequest.result;
|
||||
if (self._enabled) {
|
||||
self._requestRegistration(assertion, endpoint);
|
||||
}
|
||||
|
||||
self._registering = false;
|
||||
};
|
||||
|
||||
pushRequest.onerror = function fmd_push_error_handler() {
|
||||
DUMP('findmydevice push request failed!');
|
||||
|
||||
self._registering = false;
|
||||
self._countRegistrationRetry();
|
||||
};
|
||||
},
|
||||
|
||||
_requestRegistration: function fmd_request_registration(assertion, endpoint) {
|
||||
var obj = {
|
||||
pushurl: endpoint,
|
||||
accepts: Commands.getEnabledCommands()
|
||||
};
|
||||
|
||||
if (assertion != null) {
|
||||
obj.assert = assertion;
|
||||
}
|
||||
|
||||
if (this._state !== null) {
|
||||
obj.deviceid = this._state.deviceid;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
Requester.post('/register/', obj, function(response) {
|
||||
DUMP('findmydevice successfully registered: ', response);
|
||||
|
||||
asyncStorage.setItem('findmydevice-state', response, function() {
|
||||
self._registeredHelper.set(true);
|
||||
});
|
||||
}, this._handleServerError.bind(this));
|
||||
},
|
||||
|
||||
_onLogout: function fmd_fxa_onlogout() {
|
||||
DUMP('logged out of FxA');
|
||||
this._loggedIn = false;
|
||||
},
|
||||
|
||||
_onFxAError: function fmd_on_error(error) {
|
||||
DUMP('FxA error: ' + error);
|
||||
|
||||
if (!this._fxaReady) {
|
||||
// FIXME(ggp) workaround for bug 1040935, if FxA errors out
|
||||
// while we are initializing (usually due to an unverified
|
||||
// account), just give up for now.
|
||||
window.close();
|
||||
}
|
||||
|
||||
if (this._refreshingClientID) {
|
||||
this._cancelClientIDRefresh();
|
||||
}
|
||||
|
||||
this._scheduleAlarm('retry');
|
||||
},
|
||||
|
||||
_cancelClientIDRefresh: function fmd_cancel_clientid_refresh() {
|
||||
this._disableAttempt = false;
|
||||
this._refreshingClientID = false;
|
||||
|
||||
// If we've been woken up by the Settings app because of a disable
|
||||
// attempt, it expects us to change these two settings at the end
|
||||
// of the operation, so set them to their current values if we're
|
||||
// canceling to make sure the state in the Settings is updated
|
||||
// properly.
|
||||
this._enabledHelper.set(this._enabled);
|
||||
this._currentClientIDHelper.set(this._currentClientID);
|
||||
|
||||
// XXX(ggp) no need to unlock the 'currentClient' lock here, let
|
||||
// _onClientIDChanged do it for us
|
||||
},
|
||||
|
||||
_scheduleAlarm: function fmd_schedule_alarm(mode) {
|
||||
var nextAlarm = new Date();
|
||||
|
||||
if (mode === 'ping') {
|
||||
// this is just a regular ping to the server to make
|
||||
// sure we're still registered, so use a long interval
|
||||
nextAlarm.setHours(nextAlarm.getHours() + 6);
|
||||
} else if (mode === 'retry') {
|
||||
// something went wrong when registering or talking to the
|
||||
// server, we should check back shortly
|
||||
var interval = 1 + Math.floor(5 * Math.random());
|
||||
nextAlarm.setMinutes(nextAlarm.getMinutes() + interval);
|
||||
} else {
|
||||
DUMP('invalid alarm mode!');
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var request = navigator.mozAlarms.getAll();
|
||||
request.onsuccess = function fmd_alarms_get_all() {
|
||||
this.result.forEach(function(alarm) {
|
||||
navigator.mozAlarms.remove(alarm.id);
|
||||
});
|
||||
|
||||
var data = {type: 'findmydevice-alarm'};
|
||||
var request = navigator.mozAlarms.add(nextAlarm, 'honorTimezone', data);
|
||||
request.onsuccess = function() {
|
||||
self.endHighPriority('clientLogic');
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
_replyAndFetchCommands: function fmd_reply_and_fetch() {
|
||||
this._reply.has_passcode = Commands.deviceHasPasscode();
|
||||
Requester.post(
|
||||
this._getCommandEndpoint(),
|
||||
this._reply,
|
||||
this._handleServerResponse.bind(this),
|
||||
this._handleServerError.bind(this));
|
||||
|
||||
this._reply = {};
|
||||
},
|
||||
|
||||
_onRegisteredChanged: function fmd_registered_changed(event) {
|
||||
this._registered = event.settingValue;
|
||||
DUMP('registered: ' + this._registered);
|
||||
|
||||
if (!this._registered) {
|
||||
this._contactServer();
|
||||
this.endHighPriority('clientLogic');
|
||||
} else {
|
||||
this._loadState((function() {
|
||||
this._contactServer();
|
||||
this._currentClientIDHelper.set('');
|
||||
// XXX(ggp) we're still holding a 'clientLogic' lock, but let
|
||||
// _onClientIDChanged or _onCanDisableChanged release it (depending
|
||||
// on whether we're logged in or not).
|
||||
}).bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
_onEnabledChanged: function fmd_enabled_changed(event) {
|
||||
// No need to contact the server here if we've been enabled,
|
||||
// since that means we'll get a wake up request from the System
|
||||
// app
|
||||
this._enabled = event.settingValue;
|
||||
DUMP('enabled: ' + this._enabled);
|
||||
},
|
||||
|
||||
_onClientIDChanged: function fmd_client_id_changed(event) {
|
||||
this._currentClientID = event.settingValue;
|
||||
DUMP('current id set to: ', this._currentClientID);
|
||||
|
||||
if (this._loggedIn && this._currentClientID === '') {
|
||||
this._refreshClientIDIfRegistered(false);
|
||||
this.endHighPriority('clientLogic');
|
||||
} else if (this._registered) {
|
||||
this._canDisableHelper.set(
|
||||
this._loggedIn &&
|
||||
this._currentClientID === this._state.clientid);
|
||||
} else {
|
||||
this.endHighPriority('clientLogic');
|
||||
}
|
||||
},
|
||||
|
||||
_onCanDisableChanged: function fmd_can_disable_changed(event) {
|
||||
if (event.settingValue === true && this._disableAttempt) {
|
||||
this._enabledHelper.set(false);
|
||||
}
|
||||
|
||||
this._disableAttempt = false;
|
||||
this.endHighPriority('clientLogic');
|
||||
},
|
||||
|
||||
_refreshClientIDIfRegistered: function fmd_refresh_client_id(forceReauth) {
|
||||
DUMP('refreshing client id if registered and logged in: ',
|
||||
{registered: this._registered, loggedIn: this._loggedIn});
|
||||
|
||||
if (!this._registered || !this._loggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
DUMP('requesting assertion to refresh client id, forceReauth: ' +
|
||||
forceReauth);
|
||||
this.beginHighPriority('clientLogic');
|
||||
|
||||
this._refreshingClientID = true;
|
||||
var mozIdRequestOptions = {};
|
||||
if (forceReauth) {
|
||||
mozIdRequestOptions.refreshAuthentication = 0;
|
||||
mozIdRequestOptions.oncancel = this._cancelClientIDRefresh.bind(this);
|
||||
}
|
||||
|
||||
navigator.mozId.request(mozIdRequestOptions);
|
||||
},
|
||||
|
||||
_onLockscreenClosed: function fmd_on_lockscreen_closed() {
|
||||
if (Commands.deviceHasPasscode()) {
|
||||
DUMP('cancelling ring and track');
|
||||
Commands.invokeCommand('ring', [0]);
|
||||
Commands.invokeCommand('track', [0]);
|
||||
}
|
||||
},
|
||||
|
||||
_fetchClientID: function fmd_fetch_client_id(assertion) {
|
||||
Requester.post('/validate/', {assert: assertion},
|
||||
this._onClientIDResponse.bind(this),
|
||||
this._onClientIDServerError.bind(this));
|
||||
this._refreshingClientID = false;
|
||||
},
|
||||
|
||||
_onClientIDResponse: function fmd_on_client_id(response) {
|
||||
DUMP('got clientid reponse: ', response);
|
||||
|
||||
if (response.valid) {
|
||||
this._currentClientIDHelper.set(response.uid);
|
||||
return;
|
||||
}
|
||||
|
||||
DUMP('failed to verify assertion for client id!');
|
||||
this._scheduleAlarm('retry');
|
||||
},
|
||||
|
||||
_onClientIDServerError: function fmd_on_client_id_error(err) {
|
||||
DUMP('failed to fetch client id with status: ' + err.status);
|
||||
this._scheduleAlarm('retry');
|
||||
this._disableAttempt = false;
|
||||
},
|
||||
|
||||
_handleServerResponse: function(response, testing) {
|
||||
if (response && (this._enabled || testing)) {
|
||||
this._processCommands(response);
|
||||
}
|
||||
|
||||
this._scheduleAlarm('ping');
|
||||
},
|
||||
|
||||
_processCommands: function fmd_process_commands(cmdobj) {
|
||||
function noop() {} // callback for testing
|
||||
|
||||
for (var cmd in cmdobj) {
|
||||
// map server (short) commands to methods in the
|
||||
// commands object, and parse the arguments.
|
||||
var argsObj = cmdobj[cmd], command, args;
|
||||
switch (cmd) {
|
||||
case 'e':
|
||||
command = 'erase';
|
||||
args = [];
|
||||
break;
|
||||
case 'k':
|
||||
command = 'killswitch';
|
||||
args = [];
|
||||
break;
|
||||
case 'l':
|
||||
command = 'lock';
|
||||
args = [argsObj.m, argsObj.c];
|
||||
break;
|
||||
case 'r':
|
||||
command = 'ring';
|
||||
args = [parseInt(argsObj.d, 10)];
|
||||
break;
|
||||
case 't':
|
||||
command = 'track';
|
||||
args = [parseInt(argsObj.d, 10)];
|
||||
break;
|
||||
default:
|
||||
this._replyCallback(cmd, false, 'command not available');
|
||||
return;
|
||||
}
|
||||
|
||||
DUMP('command ' + cmd + ', args ' + JSON.stringify(args));
|
||||
|
||||
// add the callback as the last argument
|
||||
if (cmdobj.testing !== true) {
|
||||
args.push(this._replyCallback.bind(this, cmd));
|
||||
} else {
|
||||
args.push(noop);
|
||||
}
|
||||
|
||||
Commands.invokeCommand(command, args);
|
||||
}
|
||||
},
|
||||
|
||||
_countRegistrationRetry: function fmd_count_registration_retry (){
|
||||
this._scheduleAlarm('retry');
|
||||
|
||||
if (!navigator.onLine) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._registered) {
|
||||
this._retryCountHelper.get(count => {
|
||||
this._retryCountHelper.set((count || 0) + 1);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_handleServerError: function fmd_handle_server_error(err) {
|
||||
DUMP('findmydevice request failed with status: ' + err.status);
|
||||
if (err.status === 401 && this._registered) {
|
||||
this._registeredHelper.set(false);
|
||||
} else {
|
||||
this._countRegistrationRetry();
|
||||
}
|
||||
},
|
||||
|
||||
_replyCallback: function fmd_reply(cmd, ok, retval) {
|
||||
var value = {ok: ok};
|
||||
|
||||
if (cmd === 't' && ok === true && retval !== undefined) {
|
||||
value.la = retval.coords.latitude;
|
||||
value.lo = retval.coords.longitude;
|
||||
value.acc = retval.coords.accuracy;
|
||||
value.ti = retval.timestamp;
|
||||
} else if (ok === false) {
|
||||
value.error = retval;
|
||||
}
|
||||
|
||||
this._reply[cmd] = value;
|
||||
this._contactServer();
|
||||
},
|
||||
|
||||
_getCommandEndpoint: function fmd_get_command_endpoint() {
|
||||
return '/cmd/' + this._state.deviceid;
|
||||
},
|
||||
|
||||
beginHighPriority: function(reason) {
|
||||
DUMP('begin high priority section, reason: ', reason);
|
||||
if (Object.keys(this._highPriorityWakeLocks).indexOf(reason) === -1) {
|
||||
DUMP('unknown reason for high priority section?!');
|
||||
return;
|
||||
}
|
||||
|
||||
DUMP('acquiring one wakelock, wakelocks are: ',
|
||||
this._highPriorityWakeLocks);
|
||||
this._highPriorityWakeLocks[reason].push(
|
||||
navigator.requestWakeLock('high-priority'));
|
||||
},
|
||||
|
||||
endHighPriority: function(reason) {
|
||||
DUMP('end high priority section, reason: ', reason);
|
||||
if (!this._highPriorityWakeLocks[reason]) {
|
||||
return;
|
||||
}
|
||||
|
||||
DUMP('releasing one wakelock, wakelocks are: ',
|
||||
this._highPriorityWakeLocks);
|
||||
this._highPriorityWakeLocks[reason].pop().unlock();
|
||||
}
|
||||
};
|
||||
|
||||
FindMyDevice.init();
|
||||
507
gaia/apps/findmydevice/js/lib/hawk.js
Executable file
507
gaia/apps/findmydevice/js/lib/hawk.js
Executable file
|
|
@ -0,0 +1,507 @@
|
|||
/*
|
||||
HTTP Hawk Authentication Scheme
|
||||
Copyright (c) 2012-2013, Eran Hammer <eran@hueniverse.com>
|
||||
MIT Licensed
|
||||
*/
|
||||
|
||||
|
||||
// Declare namespace
|
||||
|
||||
var hawk = {};
|
||||
|
||||
|
||||
// Export if used as a module
|
||||
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports = hawk;
|
||||
}
|
||||
|
||||
hawk.client = {
|
||||
|
||||
// Generate an Authorization header for a given request
|
||||
|
||||
/*
|
||||
uri: 'http://example.com/resource?a=b'
|
||||
method: HTTP verb (e.g. 'GET', 'POST')
|
||||
options: {
|
||||
|
||||
// Required
|
||||
|
||||
credentials: {
|
||||
id: 'dh37fgj492je',
|
||||
key: 'aoijedoaijsdlaksjdl',
|
||||
algorithm: 'sha256' // 'sha1', 'sha256'
|
||||
},
|
||||
|
||||
// Optional
|
||||
|
||||
ext: 'application-specific', // Application specific data sent via the ext attribute
|
||||
timestamp: Date.now() / 1000, // A pre-calculated timestamp in seconds
|
||||
nonce: '2334f34f', // A pre-generated nonce
|
||||
localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided)
|
||||
payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided)
|
||||
contentType: 'application/json', // Payload content-type (ignored if hash provided)
|
||||
hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash
|
||||
app: '24s23423f34dx', // Oz application id
|
||||
dlg: '234sz34tww3sd' // Oz delegated-by application id
|
||||
}
|
||||
*/
|
||||
|
||||
header: function (uri, method, options) {
|
||||
|
||||
var result = {
|
||||
field: '',
|
||||
artifacts: {}
|
||||
};
|
||||
|
||||
// Validate inputs
|
||||
|
||||
if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') ||
|
||||
!method || typeof method !== 'string' ||
|
||||
!options || typeof options !== 'object') {
|
||||
|
||||
result.err = 'Invalid argument type';
|
||||
return result;
|
||||
}
|
||||
|
||||
// Application time
|
||||
|
||||
var timestamp = options.timestamp || Math.floor((hawk.utils.now() + (options.localtimeOffsetMsec || 0)) / 1000)
|
||||
|
||||
// Validate credentials
|
||||
|
||||
var credentials = options.credentials;
|
||||
if (!credentials ||
|
||||
!credentials.id ||
|
||||
!credentials.key ||
|
||||
!credentials.algorithm) {
|
||||
|
||||
result.err = 'Invalid credential object';
|
||||
return result;
|
||||
}
|
||||
|
||||
if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
|
||||
result.err = 'Unknown algorithm';
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse URI
|
||||
|
||||
if (typeof uri === 'string') {
|
||||
uri = hawk.utils.parseUri(uri);
|
||||
}
|
||||
|
||||
// Calculate signature
|
||||
|
||||
var artifacts = {
|
||||
ts: timestamp,
|
||||
nonce: options.nonce || hawk.utils.randomString(6),
|
||||
method: method,
|
||||
resource: uri.relative,
|
||||
host: uri.hostname,
|
||||
port: uri.port,
|
||||
hash: options.hash,
|
||||
ext: options.ext,
|
||||
app: options.app,
|
||||
dlg: options.dlg
|
||||
};
|
||||
|
||||
result.artifacts = artifacts;
|
||||
|
||||
// Calculate payload hash
|
||||
|
||||
if (!artifacts.hash &&
|
||||
options.hasOwnProperty('payload')) {
|
||||
|
||||
artifacts.hash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
|
||||
}
|
||||
|
||||
var mac = hawk.crypto.calculateMac('header', credentials, artifacts);
|
||||
|
||||
// Construct header
|
||||
|
||||
var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''; // Other falsey values allowed
|
||||
var header = 'Hawk id="' + credentials.id +
|
||||
'", ts="' + artifacts.ts +
|
||||
'", nonce="' + artifacts.nonce +
|
||||
(artifacts.hash ? '", hash="' + artifacts.hash : '') +
|
||||
(hasExt ? '", ext="' + hawk.utils.escapeHeaderAttribute(artifacts.ext) : '') +
|
||||
'", mac="' + mac + '"';
|
||||
|
||||
if (artifacts.app) {
|
||||
header += ', app="' + artifacts.app +
|
||||
(artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';
|
||||
}
|
||||
|
||||
result.field = header;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
// Validate server response
|
||||
|
||||
/*
|
||||
request: object created via 'new XMLHttpRequest()' after response received
|
||||
artifacts: object recieved from header().artifacts
|
||||
options: {
|
||||
payload: optional payload received
|
||||
required: specifies if a Server-Authorization header is required. Defaults to 'false'
|
||||
}
|
||||
*/
|
||||
|
||||
authenticate: function (request, credentials, artifacts, options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (request.getResponseHeader('www-authenticate')) {
|
||||
|
||||
// Parse HTTP WWW-Authenticate header
|
||||
|
||||
var attributes = hawk.utils.parseAuthorizationHeader(request.getResponseHeader('www-authenticate'), ['ts', 'tsm', 'error']);
|
||||
if (!attributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attributes.ts) {
|
||||
var tsm = hawk.crypto.calculateTsMac(attributes.ts, credentials);
|
||||
if (tsm !== attributes.tsm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hawk.utils.setNtpOffset(attributes.ts - Math.floor((new Date()).getTime() / 1000)); // Keep offset at 1 second precision
|
||||
}
|
||||
}
|
||||
|
||||
// Parse HTTP Server-Authorization header
|
||||
|
||||
if (!request.getResponseHeader('server-authorization') &&
|
||||
!options.required) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var attributes = hawk.utils.parseAuthorizationHeader(request.getResponseHeader('server-authorization'), ['mac', 'ext', 'hash']);
|
||||
if (!attributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var modArtifacts = {
|
||||
ts: artifacts.ts,
|
||||
nonce: artifacts.nonce,
|
||||
method: artifacts.method,
|
||||
resource: artifacts.resource,
|
||||
host: artifacts.host,
|
||||
port: artifacts.port,
|
||||
hash: attributes.hash,
|
||||
ext: attributes.ext,
|
||||
app: artifacts.app,
|
||||
dlg: artifacts.dlg
|
||||
};
|
||||
|
||||
var mac = hawk.crypto.calculateMac('response', credentials, modArtifacts);
|
||||
if (mac !== attributes.mac) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!options.hasOwnProperty('payload')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!attributes.hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var calculatedHash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, request.getResponseHeader('content-type'));
|
||||
return (calculatedHash === attributes.hash);
|
||||
},
|
||||
|
||||
message: function (host, port, message, options) {
|
||||
|
||||
// Validate inputs
|
||||
|
||||
if (!host || typeof host !== 'string' ||
|
||||
!port || typeof port !== 'number' ||
|
||||
message === null || message === undefined || typeof message !== 'string' ||
|
||||
!options || typeof options !== 'object') {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Application time
|
||||
|
||||
var timestamp = options.timestamp || Math.floor((hawk.utils.now() + (options.localtimeOffsetMsec || 0)) / 1000)
|
||||
|
||||
// Validate credentials
|
||||
|
||||
var credentials = options.credentials;
|
||||
if (!credentials ||
|
||||
!credentials.id ||
|
||||
!credentials.key ||
|
||||
!credentials.algorithm) {
|
||||
|
||||
// Invalid credential object
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate signature
|
||||
|
||||
var artifacts = {
|
||||
ts: timestamp,
|
||||
nonce: options.nonce || hawk.utils.randomString(6),
|
||||
host: host,
|
||||
port: port,
|
||||
hash: hawk.crypto.calculatePayloadHash(message, credentials.algorithm)
|
||||
};
|
||||
|
||||
// Construct authorization
|
||||
|
||||
var result = {
|
||||
id: credentials.id,
|
||||
ts: artifacts.ts,
|
||||
nonce: artifacts.nonce,
|
||||
hash: artifacts.hash,
|
||||
mac: hawk.crypto.calculateMac('message', credentials, artifacts)
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
authenticateTimestamp: function (message, credentials, updateClock) { // updateClock defaults to true
|
||||
|
||||
var tsm = hawk.crypto.calculateTsMac(message.ts, credentials);
|
||||
if (tsm !== message.tsm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (updateClock !== false) {
|
||||
hawk.utils.setNtpOffset(message.ts - Math.floor((new Date()).getTime() / 1000)); // Keep offset at 1 second precision
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
hawk.crypto = {
|
||||
|
||||
headerVersion: '1',
|
||||
|
||||
algorithms: ['sha1', 'sha256'],
|
||||
|
||||
calculateMac: function (type, credentials, options) {
|
||||
|
||||
var normalized = hawk.crypto.generateNormalizedString(type, options);
|
||||
|
||||
var hmac = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()](normalized, credentials.key);
|
||||
return hmac.toString(CryptoJS.enc.Base64);
|
||||
},
|
||||
|
||||
generateNormalizedString: function (type, options) {
|
||||
|
||||
var normalized = 'hawk.' + hawk.crypto.headerVersion + '.' + type + '\n' +
|
||||
options.ts + '\n' +
|
||||
options.nonce + '\n' +
|
||||
(options.method || '').toUpperCase() + '\n' +
|
||||
(options.resource || '') + '\n' +
|
||||
options.host.toLowerCase() + '\n' +
|
||||
options.port + '\n' +
|
||||
(options.hash || '') + '\n';
|
||||
|
||||
if (options.ext) {
|
||||
normalized += options.ext.replace('\\', '\\\\').replace('\n', '\\n');
|
||||
}
|
||||
|
||||
normalized += '\n';
|
||||
|
||||
if (options.app) {
|
||||
normalized += options.app + '\n' +
|
||||
(options.dlg || '') + '\n';
|
||||
}
|
||||
|
||||
return normalized;
|
||||
},
|
||||
|
||||
calculatePayloadHash: function (payload, algorithm, contentType) {
|
||||
|
||||
var hash = CryptoJS.algo[algorithm.toUpperCase()].create();
|
||||
hash.update('hawk.' + hawk.crypto.headerVersion + '.payload\n');
|
||||
hash.update(hawk.utils.parseContentType(contentType) + '\n');
|
||||
hash.update(payload || '');
|
||||
hash.update('\n');
|
||||
return hash.finalize().toString(CryptoJS.enc.Base64);
|
||||
},
|
||||
|
||||
calculateTsMac: function (ts, credentials) {
|
||||
|
||||
var hash = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()]('hawk.' + hawk.crypto.headerVersion + '.ts\n' + ts + '\n', credentials.key);
|
||||
return hash.toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
hawk.utils = {
|
||||
|
||||
storage: { // localStorage compatible interface
|
||||
_cache: {},
|
||||
setItem: function (key, value) {
|
||||
|
||||
hawk.utils.storage._cache[key] = value;
|
||||
},
|
||||
getItem: function (key) {
|
||||
|
||||
return hawk.utils.storage._cache[key];
|
||||
}
|
||||
},
|
||||
|
||||
setStorage: function (storage) {
|
||||
|
||||
var ntpOffset = hawk.utils.getNtpOffset() || 0;
|
||||
hawk.utils.storage = storage;
|
||||
hawk.utils.setNtpOffset(ntpOffset);
|
||||
},
|
||||
|
||||
setNtpOffset: function (offset) {
|
||||
|
||||
try {
|
||||
hawk.utils.storage.setItem('hawk_ntp_offset', offset);
|
||||
}
|
||||
catch (err) {
|
||||
console.error('[hawk] could not write to storage.');
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
getNtpOffset: function () {
|
||||
|
||||
return parseInt(hawk.utils.storage.getItem('hawk_ntp_offset') || '0', 10);
|
||||
},
|
||||
|
||||
now: function () {
|
||||
|
||||
return (new Date()).getTime() + hawk.utils.getNtpOffset();
|
||||
},
|
||||
|
||||
escapeHeaderAttribute: function (attribute) {
|
||||
|
||||
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"');
|
||||
},
|
||||
|
||||
parseContentType: function (header) {
|
||||
|
||||
if (!header) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return header.split(';')[0].replace(/^\s+|\s+$/g, '').toLowerCase();
|
||||
},
|
||||
|
||||
parseAuthorizationHeader: function (header, keys) {
|
||||
|
||||
if (!header) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var headerParts = header.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something]
|
||||
if (!headerParts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var scheme = headerParts[1];
|
||||
if (scheme.toLowerCase() !== 'hawk') {
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributesString = headerParts[2];
|
||||
if (!attributesString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributes = {};
|
||||
var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) {
|
||||
|
||||
// Check valid attribute names
|
||||
|
||||
if (keys.indexOf($1) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
|
||||
|
||||
if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
|
||||
if (attributes.hasOwnProperty($1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
attributes[$1] = $2;
|
||||
return '';
|
||||
});
|
||||
|
||||
if (verify !== '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
},
|
||||
|
||||
randomString: function (size) {
|
||||
|
||||
var randomSource = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
var len = randomSource.length;
|
||||
|
||||
var result = [];
|
||||
for (var i = 0; i < size; ++i) {
|
||||
result[i] = randomSource[Math.floor(Math.random() * len)];
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
|
||||
parseUri: function (input) {
|
||||
|
||||
// Based on: parseURI 1.2.2
|
||||
// http://blog.stevenlevithan.com/archives/parseuri
|
||||
// (c) Steven Levithan <stevenlevithan.com>
|
||||
// MIT License
|
||||
|
||||
var keys = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'hostname', 'port', 'resource', 'relative', 'pathname', 'directory', 'file', 'query', 'fragment'];
|
||||
|
||||
var uriRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?(((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?)(?:#(.*))?)/;
|
||||
var uriByNumber = uriRegex.exec(input);
|
||||
var uri = {};
|
||||
|
||||
var i = 15;
|
||||
while (i--) {
|
||||
uri[keys[i]] = uriByNumber[i] || '';
|
||||
}
|
||||
|
||||
if (uri.port === null ||
|
||||
uri.port === '') {
|
||||
|
||||
uri.port = (uri.protocol.toLowerCase() === 'http' ? '80' : (uri.protocol.toLowerCase() === 'https' ? '443' : ''));
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Based on: Crypto-JS v3.1.2
|
||||
// Copyright (c) 2009-2013, Jeff Mott. All rights reserved.
|
||||
// http://code.google.com/p/crypto-js/
|
||||
// http://code.google.com/p/crypto-js/wiki/License
|
||||
|
||||
var CryptoJS=CryptoJS||function(h,r){var k={},l=k.lib={},n=function(){},f=l.Base={extend:function(a){n.prototype=this;var b=new n;a&&b.mixIn(a);b.hasOwnProperty("init")||(b.init=function(){b.$super.init.apply(this,arguments)});b.init.prototype=b;b.$super=this;return b},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},j=l.WordArray=f.extend({init:function(a,b){a=this.words=a||[];this.sigBytes=b!=r?b:4*a.length},toString:function(a){return(a||s).stringify(this)},concat:function(a){var b=this.words,d=a.words,c=this.sigBytes;a=a.sigBytes;this.clamp();if(c%4)for(var e=0;e<a;e++)b[c+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((c+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)b[c+e>>>2]=d[e>>>2];else b.push.apply(b,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<32-8*(b%4);a.length=h.ceil(b/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d<a;d+=4)b.push(4294967296*h.random()|0);return new j.init(b,a)}}),m=k.enc={},s=m.Hex={stringify:function(a){var b=a.words;a=a.sigBytes;for(var d=[],c=0;c<a;c++){var e=b[c>>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c+=2)d[c>>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return new j.init(d,b/2)}},p=m.Latin1={stringify:function(a){var b=a.words;a=a.sigBytes;for(var d=[],c=0;c<a;c++)d.push(String.fromCharCode(b[c>>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c++)d[c>>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return new j.init(d,b)}},t=m.Utf8={stringify:function(a){try{return decodeURIComponent(escape(p.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return p.parse(unescape(encodeURIComponent(a)))}},q=l.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new j.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=t.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,f=c/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;c=h.min(4*a,c);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);b.sigBytes-=c}return new j.init(g,c)},clone:function(){var a=f.clone.call(this);a._data=this._data.clone();return a},_minBufferSize:0});l.Hasher=q.extend({cfg:f.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){q.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,d){return(new a.init(d)).finalize(b)}},_createHmacHelper:function(a){return function(b,d){return(new u.HMAC.init(a,d)).finalize(b)}}});var u=k.algo={};return k}(Math);
|
||||
(function () { var k = CryptoJS, b = k.lib, m = b.WordArray, l = b.Hasher, d = [], b = k.algo.SHA1 = l.extend({ _doReset: function () { this._hash = new m.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520]) }, _doProcessBlock: function (n, p) { for (var a = this._hash.words, e = a[0], f = a[1], h = a[2], j = a[3], b = a[4], c = 0; 80 > c; c++) { if (16 > c) d[c] = n[p + c] | 0; else { var g = d[c - 3] ^ d[c - 8] ^ d[c - 14] ^ d[c - 16]; d[c] = g << 1 | g >>> 31 } g = (e << 5 | e >>> 27) + b + d[c]; g = 20 > c ? g + ((f & h | ~f & j) + 1518500249) : 40 > c ? g + ((f ^ h ^ j) + 1859775393) : 60 > c ? g + ((f & h | f & j | h & j) - 1894007588) : g + ((f ^ h ^ j) - 899497514); b = j; j = h; h = f << 30 | f >>> 2; f = e; e = g } a[0] = a[0] + e | 0; a[1] = a[1] + f | 0; a[2] = a[2] + h | 0; a[3] = a[3] + j | 0; a[4] = a[4] + b | 0 }, _doFinalize: function () { var b = this._data, d = b.words, a = 8 * this._nDataBytes, e = 8 * b.sigBytes; d[e >>> 5] |= 128 << 24 - e % 32; d[(e + 64 >>> 9 << 4) + 14] = Math.floor(a / 4294967296); d[(e + 64 >>> 9 << 4) + 15] = a; b.sigBytes = 4 * d.length; this._process(); return this._hash }, clone: function () { var b = l.clone.call(this); b._hash = this._hash.clone(); return b } }); k.SHA1 = l._createHelper(b); k.HmacSHA1 = l._createHmacHelper(b) })();
|
||||
(function (k) { for (var g = CryptoJS, h = g.lib, v = h.WordArray, j = h.Hasher, h = g.algo, s = [], t = [], u = function (q) { return 4294967296 * (q - (q | 0)) | 0 }, l = 2, b = 0; 64 > b;) { var d; a: { d = l; for (var w = k.sqrt(d), r = 2; r <= w; r++) if (!(d % r)) { d = !1; break a } d = !0 } d && (8 > b && (s[b] = u(k.pow(l, 0.5))), t[b] = u(k.pow(l, 1 / 3)), b++); l++ } var n = [], h = h.SHA256 = j.extend({ _doReset: function () { this._hash = new v.init(s.slice(0)) }, _doProcessBlock: function (q, h) { for (var a = this._hash.words, c = a[0], d = a[1], b = a[2], k = a[3], f = a[4], g = a[5], j = a[6], l = a[7], e = 0; 64 > e; e++) { if (16 > e) n[e] = q[h + e] | 0; else { var m = n[e - 15], p = n[e - 2]; n[e] = ((m << 25 | m >>> 7) ^ (m << 14 | m >>> 18) ^ m >>> 3) + n[e - 7] + ((p << 15 | p >>> 17) ^ (p << 13 | p >>> 19) ^ p >>> 10) + n[e - 16] } m = l + ((f << 26 | f >>> 6) ^ (f << 21 | f >>> 11) ^ (f << 7 | f >>> 25)) + (f & g ^ ~f & j) + t[e] + n[e]; p = ((c << 30 | c >>> 2) ^ (c << 19 | c >>> 13) ^ (c << 10 | c >>> 22)) + (c & d ^ c & b ^ d & b); l = j; j = g; g = f; f = k + m | 0; k = b; b = d; d = c; c = m + p | 0 } a[0] = a[0] + c | 0; a[1] = a[1] + d | 0; a[2] = a[2] + b | 0; a[3] = a[3] + k | 0; a[4] = a[4] + f | 0; a[5] = a[5] + g | 0; a[6] = a[6] + j | 0; a[7] = a[7] + l | 0 }, _doFinalize: function () { var d = this._data, b = d.words, a = 8 * this._nDataBytes, c = 8 * d.sigBytes; b[c >>> 5] |= 128 << 24 - c % 32; b[(c + 64 >>> 9 << 4) + 14] = k.floor(a / 4294967296); b[(c + 64 >>> 9 << 4) + 15] = a; d.sigBytes = 4 * b.length; this._process(); return this._hash }, clone: function () { var b = j.clone.call(this); b._hash = this._hash.clone(); return b } }); g.SHA256 = j._createHelper(h); g.HmacSHA256 = j._createHmacHelper(h) })(Math);
|
||||
(function(){var c=CryptoJS,k=c.enc.Utf8;c.algo.HMAC=c.lib.Base.extend({init:function(a,b){a=this._hasher=new a.init;"string"==typeof b&&(b=k.parse(b));var c=a.blockSize,e=4*c;b.sigBytes>e&&(b=a.finalize(b));b.clamp();for(var f=this._oKey=b.clone(),g=this._iKey=b.clone(),h=f.words,j=g.words,d=0;d<c;d++)h[d]^=1549556828,j[d]^=909522486;f.sigBytes=g.sigBytes=e;this.reset()},reset:function(){var a=this._hasher;a.reset();a.update(this._iKey)},update:function(a){this._hasher.update(a);return this},finalize:function(a){var b=this._hasher;a=b.finalize(a);b.reset();return b.finalize(this._oKey.clone().concat(a))}})})();
|
||||
(function(){var h=CryptoJS,j=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();b=[];for(var a=0;a<f;a+=3)for(var d=(e[a>>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g<f;g++)b.push(c.charAt(d>>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d<e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
|
||||
84
gaia/apps/findmydevice/js/requester.js
Executable file
84
gaia/apps/findmydevice/js/requester.js
Executable file
|
|
@ -0,0 +1,84 @@
|
|||
/* global hawk */
|
||||
/* global Config */
|
||||
/* global DUMP */
|
||||
|
||||
'use strict';
|
||||
|
||||
var Requester = {
|
||||
XHR_TIMEOUT_MS: 60000,
|
||||
|
||||
_url: null,
|
||||
|
||||
_hawkCredentials: null,
|
||||
|
||||
init: function fmdr_init() {
|
||||
this._url = Config.api_url + '/' + Config.api_version;
|
||||
},
|
||||
|
||||
setHawkCredentials: function fmdr_set_hawk_credentials(id, key) {
|
||||
this._hawkCredentials = {
|
||||
id: id,
|
||||
key: key,
|
||||
algorithm: 'sha256'
|
||||
};
|
||||
},
|
||||
|
||||
post: function fmdr_post(url, data, onsuccess, onerror) {
|
||||
url = this._url + url;
|
||||
data = JSON.stringify(data);
|
||||
DUMP('POST-ing to ' + url + ': ' + data);
|
||||
|
||||
var xhr = new XMLHttpRequest({mozSystem: true});
|
||||
xhr.open('POST', url);
|
||||
xhr.timeout = this.XHR_TIMEOUT_MS;
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
|
||||
var hawkHeader = null;
|
||||
if (this._hawkCredentials) {
|
||||
var hawkOptions = {
|
||||
credentials: this._hawkCredentials,
|
||||
contentType: 'application/json',
|
||||
payload: data
|
||||
};
|
||||
|
||||
hawkHeader = hawk.client.header(url, 'POST', hawkOptions);
|
||||
xhr.setRequestHeader('Authorization', hawkHeader.field);
|
||||
}
|
||||
|
||||
xhr.onload = function fmdr_xhr_onload() {
|
||||
var valid = true;
|
||||
if (hawkHeader !== null) {
|
||||
valid = hawk.client.authenticate(
|
||||
xhr, this._hawkCredentials, hawkHeader.artifacts,
|
||||
{payload: xhr.responseText});
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
DUMP('ignoring invalid HAWK signature');
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status == 200) {
|
||||
DUMP('successful request, response: ' + xhr.response);
|
||||
onsuccess && onsuccess(JSON.parse(xhr.responseText));
|
||||
} else if (xhr.status !== 200) {
|
||||
DUMP('request failed with status ' + xhr.status);
|
||||
onerror && onerror(xhr);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function fmd_xhr_onerror() {
|
||||
DUMP('request failed with status ' + xhr.status);
|
||||
onerror && onerror(xhr);
|
||||
};
|
||||
|
||||
xhr.ontimeout = function fmd_xhr_ontimeout() {
|
||||
DUMP('server request timed out!');
|
||||
xhr.onerror();
|
||||
};
|
||||
|
||||
xhr.send(data);
|
||||
}
|
||||
};
|
||||
|
||||
Requester.init();
|
||||
18
gaia/apps/findmydevice/jsdoc.json
Executable file
18
gaia/apps/findmydevice/jsdoc.json
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "findmydevice",
|
||||
"tags": {
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["apps/findmydevice/js/**/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"plugins/summarize"
|
||||
],
|
||||
"opts": {
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"destination": "docs/findmydevice"
|
||||
}
|
||||
}
|
||||
89
gaia/apps/findmydevice/manifest.webapp
Executable file
89
gaia/apps/findmydevice/manifest.webapp
Executable file
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"name": "Find My Device",
|
||||
"type": "certified",
|
||||
"role": "system",
|
||||
"description": "Find My Device helps find, lock and erase a lost phone",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"permissions": {
|
||||
"themeable": {},
|
||||
"alarms": {},
|
||||
"audio-channel-ringer": {},
|
||||
"device-storage:sdcard": {
|
||||
"access": "readwrite"
|
||||
},
|
||||
"device-storage:pictures": {
|
||||
"access": "readwrite"
|
||||
},
|
||||
"device-storage:videos": {
|
||||
"access": "readwrite"
|
||||
},
|
||||
"device-storage:music": {
|
||||
"access": "readwrite"
|
||||
},
|
||||
"device-storage:crashes": {
|
||||
"access": "readwrite"
|
||||
},
|
||||
"firefox-accounts": {},
|
||||
"geolocation-noprompt": {},
|
||||
"killswitch": {},
|
||||
"power": {},
|
||||
"push": {},
|
||||
"settings": {
|
||||
"access": "readwrite"
|
||||
},
|
||||
"systemXHR": {},
|
||||
"moz-extremely-unstable-and-will-change-webcomponents": {}
|
||||
},
|
||||
"locales": {
|
||||
"en-US": {
|
||||
"name": "Find My Device",
|
||||
"description": "Find My Device helps find, lock and erase a lost phone"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Localiser mon appareil",
|
||||
"description": "Localiser mon appareil vous permet de retrouver, verrouiller ou effacer un téléphone égaré"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"84": "/style/icons/find_my_device_84.png",
|
||||
"126": "/style/icons/find_my_device_126.png",
|
||||
"142": "/style/icons/find_my_device_142.png",
|
||||
"189": "/style/icons/find_my_device_189.png",
|
||||
"284": "/style/icons/find_my_device_284.png"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"alarm": "/index.html"
|
||||
},
|
||||
{
|
||||
"push": "/index.html"
|
||||
},
|
||||
{
|
||||
"push-register": "/index.html"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"findmydevice-wakeup": {
|
||||
"description": "Wake up Find My Device from the System app",
|
||||
"rules": {
|
||||
"manifestURLs": [
|
||||
"app://system.gaiamobile.org/manifest.webapp",
|
||||
"app://settings.gaiamobile.org/manifest.webapp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"findmydevice-test": {
|
||||
"description": "Manually trigger commands for testing",
|
||||
"rules": {
|
||||
"manifestURLs": [
|
||||
"app://test-findmydevice.gaiamobile.org/manifest.webapp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
gaia/apps/findmydevice/package.json
Executable file
10
gaia/apps/findmydevice/package.json
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"webide": {
|
||||
"prepackage": {
|
||||
"command": "make",
|
||||
"env": ["APP=findmydevice"],
|
||||
"cwd": "../.."
|
||||
},
|
||||
"packageDir": "../../build_stage/findmydevice/"
|
||||
}
|
||||
}
|
||||
BIN
gaia/apps/findmydevice/style/icons/find_my_device_126.png
Executable file
BIN
gaia/apps/findmydevice/style/icons/find_my_device_126.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
gaia/apps/findmydevice/style/icons/find_my_device_142.png
Executable file
BIN
gaia/apps/findmydevice/style/icons/find_my_device_142.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
gaia/apps/findmydevice/style/icons/find_my_device_189.png
Executable file
BIN
gaia/apps/findmydevice/style/icons/find_my_device_189.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
gaia/apps/findmydevice/style/icons/find_my_device_284.png
Executable file
BIN
gaia/apps/findmydevice/style/icons/find_my_device_284.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
gaia/apps/findmydevice/style/icons/find_my_device_84.png
Executable file
BIN
gaia/apps/findmydevice/style/icons/find_my_device_84.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3 KiB |
27
gaia/apps/findmydevice/test/build/integration/findmydevice_test.js
Executable file
27
gaia/apps/findmydevice/test/build/integration/findmydevice_test.js
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true, mocha: true */
|
||||
/* global suiteSetup */
|
||||
|
||||
var helper = require('helper');
|
||||
|
||||
suite('Findmydevice tests', function() {
|
||||
suiteSetup(helper.cleanupWorkspace);
|
||||
teardown(helper.cleanupWorkspace);
|
||||
|
||||
test('APP=findmydevice make', function(done) {
|
||||
helper.exec('APP=findmydevice make', function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var zipPath = process.cwd() +
|
||||
'/profile/webapps/findmydevice.gaiamobile.org/application.zip';
|
||||
var checkList = ['manifest.webapp'];
|
||||
|
||||
checkList.forEach(function(filePath) {
|
||||
helper.checkFilePathInZip(zipPath, filePath);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
17
gaia/apps/findmydevice/test/marionette/.jshintrc
Executable file
17
gaia/apps/findmydevice/test/marionette/.jshintrc
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"marionette",
|
||||
"marionetteScriptFinished",
|
||||
"require",
|
||||
"define",
|
||||
"requireApp",
|
||||
"sinon"
|
||||
]
|
||||
}
|
||||
106
gaia/apps/findmydevice/test/marionette/findmydevice_test.js
Executable file
106
gaia/apps/findmydevice/test/marionette/findmydevice_test.js
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
marionette('Find My Device lock >', function() {
|
||||
var assert = require('assert');
|
||||
|
||||
var FINDMYDEVICE_TEST_APP = 'app://test-findmydevice.gaiamobile.org';
|
||||
var SETTINGS_DIGEST_VALUE = 'lockscreen.passcode-lock.digest.value';
|
||||
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
apps: {
|
||||
'test-findmydevice.gaiamobile.org':
|
||||
__dirname + '/fixtures/test-findmydevice',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
// launch the test app
|
||||
client.apps.launch(FINDMYDEVICE_TEST_APP);
|
||||
client.apps.switchToApp(FINDMYDEVICE_TEST_APP);
|
||||
client.helper.waitForElement('body');
|
||||
});
|
||||
|
||||
test('Lock the screen through the test app', function() {
|
||||
var messageText = 'This phone is lost.';
|
||||
var messageInput = client.findElement('input[name="message"]');
|
||||
messageInput.sendKeys(messageText);
|
||||
|
||||
client.switchToFrame();
|
||||
var oldDigest = client.settings.get(SETTINGS_DIGEST_VALUE);
|
||||
client.apps.switchToApp(FINDMYDEVICE_TEST_APP);
|
||||
var passcode = '4567';
|
||||
var passcodeInput = client.findElement('input[name="code"]');
|
||||
passcodeInput.sendKeys(passcode);
|
||||
|
||||
var lockButton = client.findElement('#lock');
|
||||
lockButton.click();
|
||||
|
||||
// XXX: After we make LockScreen as an iframe or app, this should be the
|
||||
// line to switch to LockScreen frame.
|
||||
client.switchToFrame();
|
||||
var lockscreen = client.findElement('#lockscreen');
|
||||
client.waitFor(function() {
|
||||
return lockscreen.displayed();
|
||||
});
|
||||
|
||||
var lockscreenMessage = client.findElement('#lockscreen-message');
|
||||
client.waitFor(function() {
|
||||
var text = lockscreenMessage.text();
|
||||
if (text !== '') {
|
||||
assert.equal(lockscreenMessage.text(), messageText);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// sanity-check the settings we should have set
|
||||
var settings = {
|
||||
'lockscreen.enabled': true,
|
||||
'lockscreen.passcode-lock.enabled': true,
|
||||
'lockscreen.notifications-preview.enabled': false,
|
||||
'lockscreen.lock-message': messageText,
|
||||
};
|
||||
for (var s in settings) {
|
||||
assert.equal(client.settings.get(s), settings[s]);
|
||||
}
|
||||
// checking if passcode was changed:
|
||||
var newDigest = client.settings.get(SETTINGS_DIGEST_VALUE);
|
||||
assert.notEqual(newDigest, oldDigest);
|
||||
|
||||
client.switchToFrame();
|
||||
|
||||
// now unlock the screen and re-lock it, the message should disappear
|
||||
client.executeScript(function() {
|
||||
window.wrappedJSObject.Service.request('unlock');
|
||||
});
|
||||
|
||||
client.waitFor(function() {
|
||||
return client.executeScript(function() {
|
||||
return !window.wrappedJSObject.Service.locked;
|
||||
});
|
||||
});
|
||||
|
||||
client.executeScript(function() {
|
||||
window.wrappedJSObject.Service.request('lock');
|
||||
});
|
||||
|
||||
// XXX: After we make LockScreen as an iframe or app, this should be the
|
||||
// line to switch to LockScreen frame.
|
||||
client.switchToFrame();
|
||||
lockscreen = client.findElement('#lockscreen');
|
||||
client.waitFor(function() {
|
||||
return lockscreen.displayed();
|
||||
});
|
||||
|
||||
lockscreenMessage = client.findElement('#lockscreen-message');
|
||||
client.waitFor(function() {
|
||||
return !lockscreenMessage.displayed();
|
||||
});
|
||||
|
||||
assert.equal(client.settings.get('lockscreen.lock-message'), '');
|
||||
});
|
||||
});
|
||||
38
gaia/apps/findmydevice/test/marionette/fixtures/test-findmydevice/index.html
Executable file
38
gaia/apps/findmydevice/test/marionette/fixtures/test-findmydevice/index.html
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<title>Find My Device Test App</title>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="js/findmydevice.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="buttons">
|
||||
<button id="track">Track</button>
|
||||
<button id="erase">Erase</button>
|
||||
<button id="lock">Lock</button>
|
||||
<button id="ring">Ring</button>
|
||||
</div>
|
||||
|
||||
<div id="options">
|
||||
<label>Duration <input name="duration" type="number" placeholder="60"/></label>
|
||||
<label>Message <input name="message" type="text" placeholder="Help! I'm lost!"/></label>
|
||||
<label>Passcode <input name="code" type="text" placeholder="0000"/></label>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
window.onload = function() {
|
||||
var systemPort = null;
|
||||
navigator.mozApps.getSelf().onsuccess = function() {
|
||||
var app = this.result;
|
||||
app.connect('findmydevice-test').then(function(ports) {
|
||||
if (ports.length !== 1) {
|
||||
console.error('got more than one connection?');
|
||||
return;
|
||||
}
|
||||
|
||||
systemPort = ports[0];
|
||||
}, function(err) {
|
||||
console.error('failed to connect: ' + err);
|
||||
});
|
||||
};
|
||||
|
||||
var buttons = document.getElementById('buttons');
|
||||
buttons.addEventListener('click', function(event) {
|
||||
var cmdobj = {};
|
||||
var command = event.target.id[0];
|
||||
cmdobj[command] = {};
|
||||
|
||||
var options = document.getElementsByTagName('input');
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
cmdobj[command][options[i].name[0]] = options[i].value;
|
||||
}
|
||||
|
||||
systemPort.postMessage(cmdobj);
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Test Find My Device",
|
||||
"description": "Manually issue commands for Find My Device",
|
||||
"type": "certified",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
17
gaia/apps/findmydevice/test/unit/.jshintrc
Executable file
17
gaia/apps/findmydevice/test/unit/.jshintrc
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"require",
|
||||
"requireApp",
|
||||
"sinon",
|
||||
"mocha",
|
||||
"loadBodyHTML"
|
||||
]
|
||||
}
|
||||
506
gaia/apps/findmydevice/test/unit/findmydevice_commands_test.js
Executable file
506
gaia/apps/findmydevice/test/unit/findmydevice_commands_test.js
Executable file
|
|
@ -0,0 +1,506 @@
|
|||
/* global MocksHelper */
|
||||
/* global MockSettingsListener */
|
||||
/* global MockGeolocation */
|
||||
/* global Commands */
|
||||
/* global FindMyDevice */
|
||||
/* global MockNavigatorSettings */
|
||||
/* global PasscodeHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mocks_helper.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_url.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_listener.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_helper.js');
|
||||
require('/shared/test/unit/mocks/mock_audio.js');
|
||||
require('/shared/test/unit/mocks/mock_device_storage.js');
|
||||
require('/shared/test/unit/mocks/mock_geolocation.js');
|
||||
require('/shared/js/passcode_helper.js');
|
||||
|
||||
var mocksForFindMyDevice = new MocksHelper([
|
||||
'SettingsListener', 'SettingsURL', 'SettingsHelper', 'Audio',
|
||||
'DeviceStorage', 'Geolocation'
|
||||
]).init();
|
||||
|
||||
suite('FindMyDevice >', function() {
|
||||
var realMozPower;
|
||||
var realKillSwitch;
|
||||
var realMozApps;
|
||||
var fakeClock;
|
||||
var realMozSettings;
|
||||
|
||||
mocksForFindMyDevice.attachTestHelpers();
|
||||
|
||||
var subject;
|
||||
setup(function(done) {
|
||||
realMozPower = navigator.mozPower;
|
||||
navigator.mozPower = {
|
||||
factoryReset: function(reason) {},
|
||||
};
|
||||
|
||||
this.sinon.spy(navigator.mozPower, 'factoryReset');
|
||||
|
||||
realKillSwitch = navigator.mozKillSwitch;
|
||||
navigator.mozKillSwitch = {
|
||||
enable: function() {}
|
||||
};
|
||||
|
||||
realMozApps = navigator.mozApps;
|
||||
navigator.mozApps = {
|
||||
getSelf: function() {
|
||||
var app = {
|
||||
manifestURL: 'app://findmydevice.gaiamobile.org/manifest.webapp',
|
||||
origin: 'app://findmydevice.gaiamobile.org/'
|
||||
};
|
||||
|
||||
var request = {result: app};
|
||||
setTimeout(function() {
|
||||
request.onsuccess.call(request);
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
};
|
||||
|
||||
// replace shared/js/dump.js
|
||||
window.DUMP = function() {};
|
||||
|
||||
fakeClock = this.sinon.useFakeTimers();
|
||||
|
||||
realMozSettings = navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
|
||||
window.FindMyDevice = {
|
||||
beginHighPriority: this.sinon.stub(),
|
||||
endHighPriority: this.sinon.stub()
|
||||
};
|
||||
|
||||
require('/js/commands.js', function() {
|
||||
subject = Commands;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('Ring command', function() {
|
||||
var duration = 2;
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(window, 'setTimeout');
|
||||
this.sinon.spy(window, 'clearTimeout');
|
||||
});
|
||||
|
||||
test('Basic invocation', function() {
|
||||
var ringtone = 'user selected ringtone';
|
||||
|
||||
MockSettingsListener.mCallbacks['dialer.ringtone'](ringtone);
|
||||
|
||||
Commands.invokeCommand('ring', [duration, function(retval) {
|
||||
var lock = MockSettingsListener.getSettingsLock().locks.pop();
|
||||
|
||||
var channel = Commands._ringer.mozAudioChannelType;
|
||||
assert.equal(channel, 'ringer', 'use notification channel');
|
||||
assert.equal(lock['audio.volume.notification'], 15,
|
||||
'volume set to maximum');
|
||||
assert.equal(Commands._ringer.paused, false, 'must be playing');
|
||||
assert.equal(Commands._ringer.src, ringtone, 'must use ringtone');
|
||||
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
}]);
|
||||
|
||||
fakeClock.tick(duration * 1000);
|
||||
assert.equal(Commands._ringer.paused, true, 'must have stopped');
|
||||
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
|
||||
});
|
||||
|
||||
test('Stop ringing if duration is zero', function() {
|
||||
Commands.invokeCommand('ring', [duration, function(retval) {
|
||||
assert.equal(Commands._ringer.paused, false, 'must be playing');
|
||||
sinon.assert.called(window.setTimeout);
|
||||
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.notCalled(FindMyDevice.endHighPriority);
|
||||
}]);
|
||||
|
||||
fakeClock.tick(duration/2 * 1000);
|
||||
|
||||
var timeoutId = Commands._ringTimeoutId;
|
||||
assert.isFalse(Commands._ringer.paused, 'must not be done ringing');
|
||||
Commands.invokeCommand('ring', [0, function(retval) {}]);
|
||||
|
||||
assert.equal(Commands._ringer.paused, true, 'must have stopped');
|
||||
assert.isNull(Commands._ringTimeoutId, 'timeout must have been canceled');
|
||||
sinon.assert.calledWith(window.clearTimeout, timeoutId);
|
||||
sinon.assert.calledTwice(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
sinon.assert.calledTwice(FindMyDevice.endHighPriority);
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
|
||||
});
|
||||
|
||||
test('Should do nothing if already ringing', function() {
|
||||
Commands.invokeCommand('ring', [duration, function(retval) {
|
||||
assert.isFalse(Commands._ringer.paused, 'must be playing');
|
||||
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.notCalled(FindMyDevice.endHighPriority);
|
||||
}]);
|
||||
|
||||
fakeClock.tick(duration/2 * 1000);
|
||||
|
||||
assert.isFalse(Commands._ringer.paused, 'must not be done ringing');
|
||||
Commands.invokeCommand('ring', [duration, function(retval) {}]);
|
||||
|
||||
fakeClock.tick(duration/2 * 1000);
|
||||
|
||||
assert.isTrue(Commands._ringer.paused, 'must have stopped');
|
||||
assert.isNull(Commands._ringTimeoutId, 'no timeouts should be set');
|
||||
sinon.assert.calledTwice(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
sinon.assert.calledTwice(FindMyDevice.endHighPriority);
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
|
||||
});
|
||||
|
||||
test('Should do nothing if not ringing and duration is zero', function() {
|
||||
Commands.invokeCommand('ring', [0, function(retval) {}]);
|
||||
assert.isTrue(Commands._ringer.paused, 'must not have started');
|
||||
assert.isNull(Commands._ringTimeoutId);
|
||||
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
|
||||
});
|
||||
});
|
||||
|
||||
test('Erase: ensure factoryReset is called with "wipe"', function(done) {
|
||||
subject.invokeCommand('erase', [function(retval, error) {
|
||||
assert.equal(navigator.mozPower.factoryReset.calledWith('wipe'), true);
|
||||
done();
|
||||
}]);
|
||||
});
|
||||
|
||||
suite('Track command', function() {
|
||||
test('Track receives positions correctly', function() {
|
||||
// track for a period of time
|
||||
var duration = (subject.TRACK_UPDATE_INTERVAL_MS) / 1000;
|
||||
subject.invokeCommand('track', [duration, function(retval, position) {
|
||||
assert.equal(retval, true);
|
||||
// ensure, while tracking, the locations match what the mock makes
|
||||
assert.deepEqual(position, MockGeolocation.fakePosition ,
|
||||
'check the position is what the mock provided');
|
||||
}]);
|
||||
fakeClock.tick(2* subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
});
|
||||
|
||||
test('Track command', function() {
|
||||
// track for 3 intervals
|
||||
var duration = (3 * subject.TRACK_UPDATE_INTERVAL_MS) / 1000;
|
||||
subject.invokeCommand('track', [duration, function(retval, position) {}]);
|
||||
|
||||
// after 2 intervals...
|
||||
fakeClock.tick(2 * subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
// we're mid-track, check we have a watch and timeout IDs
|
||||
assert.notEqual(subject._watchPositionId, null);
|
||||
assert.notEqual(subject._trackTimeoutId, null);
|
||||
|
||||
// after another interval...
|
||||
fakeClock.tick(1 * subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
// .. tracking should have stopped.
|
||||
// ensure we no longer have track and watch IDs.
|
||||
assert.equal(subject._trackTimeoutId, null);
|
||||
assert.equal(subject._watchPositionId, null);
|
||||
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
sinon.assert.alwaysCalledWith(FindMyDevice.endHighPriority, 'command');
|
||||
|
||||
// check wakelock count
|
||||
var begin = FindMyDevice.beginHighPriority.callCount;
|
||||
var end = FindMyDevice.beginHighPriority.callCount;
|
||||
assert.equal(begin, end, 'begin and end count should match');
|
||||
});
|
||||
|
||||
// We assume for this test that the interval used by the geolocation mock
|
||||
// is significantly smaller than TRACK_UPDATE_INTERVAL.
|
||||
test('Track command should throttle observed positions', function() {
|
||||
// track for a long duration (e.g. 10 intervals)
|
||||
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
|
||||
var positions = 0;
|
||||
subject.invokeCommand('track', [duration, function(retval, position) {
|
||||
positions++;
|
||||
}]);
|
||||
|
||||
// set the clock to just before the interval, ensure no locations are seen
|
||||
var amountBefore = 2;
|
||||
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS - amountBefore);
|
||||
assert.equal(positions, 0, 'no update intervals should have passed');
|
||||
// set the clock to almost two intervals, ensure only one location is seen
|
||||
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
assert.equal(positions, 1, '1 update interval should have passed');
|
||||
});
|
||||
|
||||
test('Track command should update its duration if invoked while running',
|
||||
function() {
|
||||
// track for a long duration (e.g. 10 intervals)
|
||||
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
|
||||
var positions = 0;
|
||||
subject.invokeCommand('track', [duration, function(retval, position) {
|
||||
positions++;
|
||||
}]);
|
||||
|
||||
// ensure the position count updates as expected (one position per
|
||||
// interval)
|
||||
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
assert.equal(positions, 1, '1 update interval should have passed');
|
||||
|
||||
// reset the duration to a shorter value:
|
||||
duration = 2 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
|
||||
subject.invokeCommand('track', [duration, function(retval, position) {
|
||||
positions++;
|
||||
}]);
|
||||
|
||||
// allow the timer to progress to after the duration completes
|
||||
fakeClock.tick(5 * subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
|
||||
// ensure tracking stopped at the end of the new duration:
|
||||
// position count should be at 3
|
||||
assert.equal(positions, 3, '2 more (3) intervals should have passed');
|
||||
|
||||
// and the track and watch IDs should be null
|
||||
assert.equal(subject._trackTimeoutId, null);
|
||||
assert.equal(subject._watchPositionId, null);
|
||||
|
||||
// check wakelock count
|
||||
var begin = FindMyDevice.beginHighPriority.callCount;
|
||||
var end = FindMyDevice.beginHighPriority.callCount;
|
||||
assert.equal(begin, end, 'begin and end count should match');
|
||||
});
|
||||
|
||||
test('Track command should stop if duration is zero',
|
||||
function() {
|
||||
// track for a long duration (e.g. 10 intervals)
|
||||
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
|
||||
var positions = 0;
|
||||
subject.invokeCommand('track', [duration, function(retval, position) {
|
||||
positions++;
|
||||
}]);
|
||||
|
||||
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
|
||||
// after a single interval, stop the tracking
|
||||
subject.invokeCommand('track', [0, function(retval) {
|
||||
assert.equal(retval, true);
|
||||
}]);
|
||||
|
||||
// allow more intervals to pass
|
||||
fakeClock.tick(2 * subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
|
||||
// and ensure the position count remains at 1
|
||||
assert.equal(positions, 1);
|
||||
|
||||
// check the track and watch IDs should be null
|
||||
assert.equal(subject._trackTimeoutId, null);
|
||||
assert.equal(subject._watchPositionId, null);
|
||||
});
|
||||
|
||||
test('track should not leak wakelocks with nonzero duration', function() {
|
||||
// start tracking
|
||||
var duration = 9 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
|
||||
subject.invokeCommand('track', [duration, function() {}]);
|
||||
|
||||
// once tracking is complete, check the wakelock call counts match
|
||||
fakeClock.tick(10 * subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
var begin = FindMyDevice.beginHighPriority.callCount;
|
||||
var end = FindMyDevice.beginHighPriority.callCount;
|
||||
assert.equal(begin, end, 'begin and end count should match');
|
||||
});
|
||||
|
||||
test('track should not leak wakelocks with zero duration', function() {
|
||||
subject.invokeCommand('track', [0, function() {}]);
|
||||
var begin = FindMyDevice.beginHighPriority.callCount;
|
||||
var end = FindMyDevice.beginHighPriority.callCount;
|
||||
assert.equal(begin, end, 'begin and end count should match');
|
||||
});
|
||||
|
||||
test('track should not leak wakelocks if stopped mid-track', function() {
|
||||
// track for a long duration (e.g. 10 intervals)
|
||||
var duration = 10 * subject.TRACK_UPDATE_INTERVAL_MS / 1000;
|
||||
subject.invokeCommand('track', [duration, function() {}]);
|
||||
|
||||
// after a single interval, stop the tracking
|
||||
fakeClock.tick(subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
subject.invokeCommand('track', [0, function(retval) {}]);
|
||||
|
||||
// go past the original duration
|
||||
fakeClock.tick(10 * subject.TRACK_UPDATE_INTERVAL_MS);
|
||||
|
||||
// check the wakelock call counts
|
||||
var begin = FindMyDevice.beginHighPriority.callCount;
|
||||
var end = FindMyDevice.beginHighPriority.callCount;
|
||||
assert.equal(begin, end, 'begin and end count should match');
|
||||
});
|
||||
});
|
||||
|
||||
test('Bug 1027325 - correctly check that passcode lock is set', function() {
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.enabled', true);
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
|
||||
'1234');
|
||||
assert.equal(true, subject.deviceHasPasscode());
|
||||
});
|
||||
|
||||
test('Bug 1027325 - correctly check that passcode lock is unset', function() {
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.enabled', false);
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
|
||||
false);
|
||||
assert.equal(false, subject.deviceHasPasscode());
|
||||
});
|
||||
|
||||
test('Bug 1027325 - correctly check that lockscreen is set, but passcode ' +
|
||||
'lock is unset', function() {
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.enabled', true);
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
|
||||
false);
|
||||
assert.equal(false, subject.deviceHasPasscode());
|
||||
});
|
||||
|
||||
suite('KillSwitch command', function() {
|
||||
suite('With API existing', function() {
|
||||
setup(function() {
|
||||
this.sinon.spy(navigator.mozKillSwitch, 'enable');
|
||||
});
|
||||
|
||||
test('Basic invocation', function(done) {
|
||||
subject.invokeCommand('killswitch', [ function(retval, error) {
|
||||
sinon.assert.calledOnce(navigator.mozKillSwitch.enable);
|
||||
|
||||
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
} ]);
|
||||
|
||||
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('Without API existing', function() {
|
||||
setup(function() {
|
||||
navigator.mozKillSwitch = undefined;
|
||||
});
|
||||
|
||||
test('No invocation', function(done) {
|
||||
subject.invokeCommand('killswitch', [ function(retval, error) {
|
||||
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
} ]);
|
||||
|
||||
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('List of accepted commands', function() {
|
||||
MockSettingsListener.mTriggerCallback('geolocation.enabled', true);
|
||||
|
||||
var allCommands = ['track', 'erase', 'ring', 'lock', 'killswitch'];
|
||||
var enabledCommands = subject.getEnabledCommands();
|
||||
assert.deepEqual(enabledCommands.sort(), allCommands.sort());
|
||||
|
||||
// track should be disabled when geolocation is disabled
|
||||
MockSettingsListener.mTriggerCallback('geolocation.enabled', false);
|
||||
allCommands = ['erase', 'ring', 'lock', 'killswitch'];
|
||||
enabledCommands = subject.getEnabledCommands();
|
||||
assert.deepEqual(enabledCommands.sort(), allCommands.sort());
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
navigator.mozPower = realMozPower;
|
||||
navigator.mozKillSwitch = realKillSwitch;
|
||||
navigator.mozApps = realMozApps;
|
||||
navigator.mozSettings = realMozSettings;
|
||||
|
||||
delete window.DUMP;
|
||||
|
||||
fakeClock.restore();
|
||||
|
||||
delete window.FindMyDevice;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
suite('FindMyDevice (with real clock) >', function() {
|
||||
var realMozSettings;
|
||||
|
||||
mocksForFindMyDevice.attachTestHelpers();
|
||||
|
||||
var subject;
|
||||
setup(function(done) {
|
||||
|
||||
// replace shared/js/dump.js
|
||||
window.DUMP = function() {};
|
||||
|
||||
realMozSettings = navigator.mozSettings;
|
||||
MockNavigatorSettings.mSetup();
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
|
||||
window.FindMyDevice = {
|
||||
beginHighPriority: this.sinon.stub(),
|
||||
endHighPriority: this.sinon.stub()
|
||||
};
|
||||
|
||||
require('/js/commands.js', function() {
|
||||
subject = Commands;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Lock command', function(done) {
|
||||
var code = '1234', message = 'locked!';
|
||||
var DIGEST_VALUE = 'lockscreen.passcode-lock.digest.value';
|
||||
var settingsLock = MockSettingsListener.getSettingsLock();
|
||||
var oldDigest = settingsLock.get(DIGEST_VALUE);
|
||||
|
||||
subject.invokeCommand('lock', [message, code, function(retval) {
|
||||
assert.equal(retval, true);
|
||||
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'command');
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'command');
|
||||
var lock = MockSettingsListener.getSettingsLock().locks.pop();
|
||||
assert.deepEqual({
|
||||
'lockscreen.enabled': true,
|
||||
'lockscreen.notifications-preview.enabled': false,
|
||||
'lockscreen.passcode-lock.enabled': true,
|
||||
'lockscreen.lock-message': message,
|
||||
'lockscreen.lock-immediately': true
|
||||
}, lock, 'check that the correct settings were set');
|
||||
assert.notEqual(oldDigest, lock[DIGEST_VALUE],
|
||||
'check that Passcode digest changed');
|
||||
|
||||
done();
|
||||
}]);
|
||||
});
|
||||
|
||||
test('Bug 1201530 - check existing passcode override', function() {
|
||||
var code = '2342', message = 'already locked!';
|
||||
var stubPasscodeSet = sinon.stub(PasscodeHelper, 'set', function() {
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
// Simulate already-locked device.
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.enabled', true);
|
||||
MockSettingsListener.mTriggerCallback('lockscreen.passcode-lock.enabled',
|
||||
true);
|
||||
subject.invokeCommand('lock', [message, code, function() {}]);
|
||||
assert.isTrue(stubPasscodeSet.calledWith(code),
|
||||
'existing passcode is not overridden');
|
||||
stubPasscodeSet.restore();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
// clean up sinon.js stubs
|
||||
navigator.mozSettings = realMozSettings;
|
||||
MockNavigatorSettings.mTeardown();
|
||||
delete window.DUMP;
|
||||
delete window.FindMyDevice;
|
||||
});
|
||||
});
|
||||
135
gaia/apps/findmydevice/test/unit/findmydevice_requester_test.js
Executable file
135
gaia/apps/findmydevice/test/unit/findmydevice_requester_test.js
Executable file
|
|
@ -0,0 +1,135 @@
|
|||
/* global MocksHelper, hawk, Requester */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_dump.js');
|
||||
|
||||
var mocksForFindMyDevice = new MocksHelper([
|
||||
'Dump'
|
||||
]).init();
|
||||
|
||||
suite('FindMyDevice >', function() {
|
||||
var fakeXHR;
|
||||
|
||||
mocksForFindMyDevice.attachTestHelpers();
|
||||
|
||||
suiteSetup(function(done) {
|
||||
window.Config = {
|
||||
api_url: 'https://find.firefox.com',
|
||||
api_version: 'v0'
|
||||
};
|
||||
|
||||
// We require requester.js here and not above because
|
||||
// we want to make sure all of our dependencies have already
|
||||
// been loaded.
|
||||
require('/js/requester.js', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
delete window.Config;
|
||||
});
|
||||
|
||||
var xhr;
|
||||
setup(function() {
|
||||
xhr = null;
|
||||
fakeXHR = sinon.useFakeXMLHttpRequest();
|
||||
fakeXHR.onCreate = function(request) {
|
||||
xhr = request;
|
||||
};
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
fakeXHR.restore();
|
||||
});
|
||||
|
||||
test('url is properly loaded from configuration', function() {
|
||||
assert.equal(Requester._url, 'https://find.firefox.com/v0');
|
||||
});
|
||||
|
||||
test('XHR is created with the correct properties', function() {
|
||||
Requester.post('/register');
|
||||
assert.equal(xhr.url, 'https://find.firefox.com/v0/register');
|
||||
assert.equal(xhr.requestHeaders['Content-Type'],
|
||||
'application/json;charset=utf-8');
|
||||
assert.equal(xhr.method, 'POST');
|
||||
assert.equal(xhr.timeout, Requester.XHR_TIMEOUT_MS);
|
||||
});
|
||||
|
||||
test('onsuccess is called on successful server response', function() {
|
||||
var response = '{"ok": true}';
|
||||
var onsuccess = this.sinon.stub();
|
||||
Requester.post('/register', {}, onsuccess);
|
||||
|
||||
xhr.respond(200, {'Content-type': 'application/json'}, response);
|
||||
sinon.assert.called(onsuccess);
|
||||
});
|
||||
|
||||
test('onerror is called on failed server response', function() {
|
||||
var onerror = this.sinon.stub();
|
||||
Requester.post('/register', {}, null, onerror);
|
||||
|
||||
xhr.respond(401, {}, '');
|
||||
sinon.assert.called(onerror);
|
||||
});
|
||||
|
||||
test('onerror is called on timeout', function() {
|
||||
var onerror = this.sinon.stub();
|
||||
Requester.post('/register', {}, null, onerror);
|
||||
|
||||
xhr.ontimeout();
|
||||
sinon.assert.called(onerror);
|
||||
});
|
||||
|
||||
suite('HAWK credentials', function() {
|
||||
var hawkOptions = {
|
||||
credentials: {id: 'id', key: 'key', algorithm: 'sha256'},
|
||||
contentType: 'application/json',
|
||||
payload: '{}'
|
||||
};
|
||||
|
||||
suiteSetup(function() {
|
||||
window.hawk = {
|
||||
client: {
|
||||
header: function() {
|
||||
return {field: 'header'};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Requester.setHawkCredentials('id', 'key');
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
delete window.hawk;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(hawk.client, 'header');
|
||||
});
|
||||
|
||||
test('HAWK credentials are included in the request', function() {
|
||||
Requester.post('/register', {});
|
||||
|
||||
sinon.assert.calledWith(hawk.client.header,
|
||||
xhr.url, 'POST', hawkOptions);
|
||||
assert.equal(xhr.requestHeaders.Authorization,
|
||||
hawk.client.header().field);
|
||||
});
|
||||
|
||||
test('ignore response if HAWK doesn\'t verify', function() {
|
||||
hawk.client.authenticate = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
var onsuccess = this.sinon.stub();
|
||||
var onerror = this.sinon.stub();
|
||||
Requester.post('/register', {}, onsuccess, onerror);
|
||||
xhr.respond(200, {'Content-type': 'application/json'}, '{}');
|
||||
|
||||
sinon.assert.notCalled(onsuccess);
|
||||
sinon.assert.notCalled(onerror);
|
||||
});
|
||||
});
|
||||
});
|
||||
826
gaia/apps/findmydevice/test/unit/findmydevice_test.js
Executable file
826
gaia/apps/findmydevice/test/unit/findmydevice_test.js
Executable file
|
|
@ -0,0 +1,826 @@
|
|||
/* global MocksHelper, MockGeolocation, MockNavigatormozSetMessageHandler,
|
||||
MockSettingsHelper, MockNavigatorSettings, FindMyDevice, MockMozAlarms,
|
||||
IAC_API_WAKEUP_REASON_LOGIN, IAC_API_WAKEUP_REASON_LOGOUT,
|
||||
IAC_API_WAKEUP_REASON_TRY_DISABLE, IAC_API_WAKEUP_REASON_ENABLED_CHANGED,
|
||||
IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED, Commands
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mocks_helper.js');
|
||||
require('/shared/test/unit/mocks/mock_dump.js');
|
||||
require('/shared/test/unit/mocks/mock_async_storage.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_listener.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_helper.js');
|
||||
require('/shared/test/unit/mocks/mock_geolocation.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_set_message_handler.js');
|
||||
require('/shared/js/findmydevice_iac_api.js');
|
||||
require('/shared/test/unit/mocks/mock_moz_alarms.js');
|
||||
|
||||
var mocksForFindMyDevice = new MocksHelper([
|
||||
'asyncStorage',
|
||||
'Dump',
|
||||
'Geolocation',
|
||||
'SettingsHelper'
|
||||
]).init();
|
||||
|
||||
suite('FindMyDevice >', function() {
|
||||
var realMozId;
|
||||
var realMozSettings;
|
||||
var realMozSetMessageHandler;
|
||||
var realMozAlarms;
|
||||
var realNavigatorOnLine;
|
||||
var realCommands;
|
||||
var observerSpy;
|
||||
|
||||
mocksForFindMyDevice.attachTestHelpers();
|
||||
|
||||
suiteSetup(function(done) {
|
||||
realMozId = navigator.mozId;
|
||||
// attempting to stub only the request method of mozId,
|
||||
// as in |sinon.stub(navigator.mozId, 'request', ...)|,
|
||||
// causes an exception to be raised, so we replace the
|
||||
// entire mozId object.
|
||||
navigator.mozId = {
|
||||
request: sinon.stub(),
|
||||
watch: sinon.stub()
|
||||
};
|
||||
|
||||
realMozSettings = navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
MockNavigatorSettings.mSetup();
|
||||
|
||||
realMozSetMessageHandler = navigator.mozSetMessageHandler;
|
||||
navigator.mozSetMessageHandler = MockNavigatormozSetMessageHandler;
|
||||
MockNavigatormozSetMessageHandler.mSetup();
|
||||
|
||||
realMozAlarms = navigator.mozAlarms;
|
||||
navigator.mozAlarms = MockMozAlarms;
|
||||
|
||||
realNavigatorOnLine = Object.getOwnPropertyDescriptor(navigator, 'onLine');
|
||||
Object.defineProperty(navigator, 'onLine', {
|
||||
fakeOnLine: true,
|
||||
configurable: true,
|
||||
get: function() { return this.fakeOnLine; },
|
||||
set: function(status) { this.fakeOnLine = !!status; }
|
||||
});
|
||||
|
||||
window.Config = {
|
||||
api_url: 'https://find.firefox.com',
|
||||
api_version: 'v0'
|
||||
};
|
||||
|
||||
observerSpy = sinon.spy(MockNavigatorSettings, 'addObserver');
|
||||
|
||||
// We require findmydevice.js here and not above because
|
||||
// we want to make sure all of our dependencies have already
|
||||
// been loaded.
|
||||
require('/js/findmydevice.js', function() {
|
||||
// We stub navigator.mozId.watch above, so FindMyDevice.init()
|
||||
// is effectively a no-op. We don't need to fully initialize FMD for
|
||||
// these tests, so just register for the events we care about below.
|
||||
FindMyDevice._observeSettings();
|
||||
FindMyDevice._initMessageHandlers();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
observerSpy.restore();
|
||||
|
||||
delete window.Config;
|
||||
|
||||
navigator.mozId = realMozId;
|
||||
|
||||
navigator.mozSettings = realMozSettings;
|
||||
MockNavigatorSettings.mTeardown();
|
||||
|
||||
navigator.mozSetMessageHandler = realMozSetMessageHandler;
|
||||
MockNavigatormozSetMessageHandler.mTeardown();
|
||||
|
||||
navigator.mozAlarms = realMozAlarms;
|
||||
|
||||
if (realNavigatorOnLine) {
|
||||
Object.defineProperty(navigator, 'onLine', realNavigatorOnLine);
|
||||
}
|
||||
});
|
||||
|
||||
setup(function(done) {
|
||||
this.sinon.stub(FindMyDevice, '_reportDisabled');
|
||||
this.sinon.stub(FindMyDevice, '_replyAndFetchCommands');
|
||||
|
||||
realCommands = window.Commands;
|
||||
window.Commands = {
|
||||
invokeCommand: this.sinon.stub(),
|
||||
deviceHasPasscode: null
|
||||
};
|
||||
|
||||
// XXX(ggp) force re-creation of the SettingsHelper objects
|
||||
// used by FMD, since MockSettingsHelper invalidates all objects
|
||||
// in its mTeardown.
|
||||
FindMyDevice._initSettings(done);
|
||||
|
||||
navigator.onLine = true;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
FindMyDevice._enabled = false;
|
||||
FindMyDevice._registered = false;
|
||||
FindMyDevice._loggedIn = false;
|
||||
FindMyDevice._state = null;
|
||||
window.Commands = realCommands;
|
||||
});
|
||||
|
||||
function sendWakeUpMessage(reason) {
|
||||
var port = {};
|
||||
MockNavigatormozSetMessageHandler.mTrigger('connection',
|
||||
{port: port, keyword: 'findmydevice-wakeup'});
|
||||
port.onmessage({data: reason});
|
||||
}
|
||||
|
||||
test('ensure retry counter is reset on enable', function() {
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_ENABLED_CHANGED);
|
||||
|
||||
MockSettingsHelper('findmydevice.retry-count').get(
|
||||
function(val) {
|
||||
assert.equal(val, 0, 'retry count should be 0');
|
||||
});
|
||||
});
|
||||
|
||||
test('retryCount is not incremented on error if registered', function() {
|
||||
FindMyDevice._registered = true;
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_ENABLED_CHANGED);
|
||||
|
||||
this.sinon.stub(FindMyDevice, 'beginHighPriority');
|
||||
this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
|
||||
// simulate 3 failed requests
|
||||
FindMyDevice._handleServerError({status:401});
|
||||
FindMyDevice._handleServerError({status:401});
|
||||
FindMyDevice._handleServerError({status:401});
|
||||
|
||||
MockSettingsHelper('findmydevice.retry-count').get(
|
||||
function(val) {
|
||||
assert.equal(val, 0, 'retry count should be 0');
|
||||
});
|
||||
});
|
||||
|
||||
test('retryCount is incremented on error when not registered', function() {
|
||||
FindMyDevice._registered = false;
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_ENABLED_CHANGED);
|
||||
|
||||
this.sinon.stub(FindMyDevice, 'beginHighPriority');
|
||||
this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
|
||||
// simulate 3 failed requests
|
||||
FindMyDevice._handleServerError({status:401});
|
||||
FindMyDevice._handleServerError({status:401});
|
||||
FindMyDevice._handleServerError({status:401});
|
||||
|
||||
MockSettingsHelper('findmydevice.retry-count').get(
|
||||
function(val) {
|
||||
assert.equal(val, 3, 'retry count should be 3');
|
||||
});
|
||||
});
|
||||
|
||||
test('retryCount is not incremented when offline', function() {
|
||||
FindMyDevice._registered = false;
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_ENABLED_CHANGED);
|
||||
|
||||
this.sinon.stub(FindMyDevice, 'beginHighPriority');
|
||||
this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
navigator.onLine = false;
|
||||
|
||||
// Simulate 3 failed requests
|
||||
FindMyDevice._handleServerError({status: 401});
|
||||
FindMyDevice._handleServerError({status: 401});
|
||||
FindMyDevice._handleServerError({status: 401});
|
||||
|
||||
MockSettingsHelper('findmydevice.retry-count').get(
|
||||
function(val) {
|
||||
assert.equal(val, 0, 'retry count should be 0');
|
||||
});
|
||||
});
|
||||
|
||||
test('fields from coordinates are included in server response', function() {
|
||||
FindMyDevice._registered = true;
|
||||
FindMyDevice._enabled = true;
|
||||
|
||||
FindMyDevice._replyCallback('t', true, MockGeolocation.fakePosition);
|
||||
sinon.assert.called(FindMyDevice._replyAndFetchCommands);
|
||||
assert.deepEqual(FindMyDevice._reply.t, {
|
||||
ok: true,
|
||||
la: MockGeolocation.fakePosition.coords.latitude,
|
||||
lo: MockGeolocation.fakePosition.coords.longitude,
|
||||
acc: MockGeolocation.fakePosition.coords.accuracy,
|
||||
ti: MockGeolocation.fakePosition.timestamp
|
||||
});
|
||||
});
|
||||
|
||||
test('error message is included in the server response', function() {
|
||||
FindMyDevice._registered = true;
|
||||
FindMyDevice._enabled = true;
|
||||
|
||||
var message = 'error message';
|
||||
FindMyDevice._replyCallback('t', false, message);
|
||||
sinon.assert.called(FindMyDevice._replyAndFetchCommands);
|
||||
assert.equal(FindMyDevice._reply.t.error, message);
|
||||
});
|
||||
|
||||
suite('settings observers', function() {
|
||||
test('Observes findmydevice.registered', function() {
|
||||
sinon.assert.calledWith(observerSpy, 'findmydevice.registered');
|
||||
assert.isTrue('_registered' in FindMyDevice);
|
||||
assert.equal('object', typeof FindMyDevice._registeredHelper);
|
||||
assert.notEqual(null, FindMyDevice._registeredHelper);
|
||||
});
|
||||
|
||||
test('Observes findmydevice.enabled', function() {
|
||||
sinon.assert.calledWith(observerSpy, 'findmydevice.enabled');
|
||||
assert.isTrue('_enabled' in FindMyDevice);
|
||||
assert.equal('object', typeof FindMyDevice._enabledHelper);
|
||||
assert.notEqual(null, FindMyDevice._enabledHelper);
|
||||
});
|
||||
|
||||
test('Observes findmydevice.current-clientid', function() {
|
||||
sinon.assert.calledWith(observerSpy, 'findmydevice.current-clientid');
|
||||
assert.isTrue('_currentClientID' in FindMyDevice);
|
||||
assert.equal('object', typeof FindMyDevice._currentClientIDHelper);
|
||||
assert.notEqual(null, FindMyDevice._currentClientIDHelper);
|
||||
});
|
||||
|
||||
test('Observes findmydevice.can-disable', function() {
|
||||
sinon.assert.calledWith(observerSpy, 'findmydevice.can-disable');
|
||||
assert.isTrue('_canDisable' in FindMyDevice);
|
||||
assert.equal('object', typeof FindMyDevice._canDisableHelper);
|
||||
assert.notEqual(null, FindMyDevice._canDisableHelper);
|
||||
});
|
||||
});
|
||||
|
||||
suite('settings handlers', function() {
|
||||
var endHighPriorityStub;
|
||||
|
||||
suite('Changes to findmydevice.registered', function() {
|
||||
var contactServerSpy, loadStateSpy, currentIdHelperSpy;
|
||||
var _registered = {};
|
||||
|
||||
setup(function() {
|
||||
contactServerSpy = this.sinon.spy(FindMyDevice, '_contactServer');
|
||||
loadStateSpy = this.sinon.spy(FindMyDevice, '_loadState');
|
||||
currentIdHelperSpy =
|
||||
this.sinon.spy(FindMyDevice._currentClientIDHelper, 'set');
|
||||
endHighPriorityStub = this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
});
|
||||
|
||||
suite('when not registered', function() {
|
||||
setup(function() {
|
||||
_registered = { settingValue: false};
|
||||
});
|
||||
|
||||
test('contact server', function() {
|
||||
FindMyDevice._onRegisteredChanged(_registered);
|
||||
sinon.assert.calledOnce(contactServerSpy);
|
||||
sinon.assert.notCalled(loadStateSpy);
|
||||
sinon.assert.notCalled(currentIdHelperSpy);
|
||||
});
|
||||
|
||||
test('end wakelock section', function() {
|
||||
FindMyDevice._onRegisteredChanged(_registered);
|
||||
sinon.assert.calledWith(endHighPriorityStub, 'clientLogic');
|
||||
sinon.assert.notCalled(loadStateSpy);
|
||||
sinon.assert.notCalled(currentIdHelperSpy);
|
||||
});
|
||||
});
|
||||
|
||||
suite('when registered', function() {
|
||||
setup(function() {
|
||||
_registered = { settingValue: true};
|
||||
});
|
||||
|
||||
test('does load state', function() {
|
||||
FindMyDevice._onRegisteredChanged(_registered);
|
||||
sinon.assert.calledOnce(loadStateSpy);
|
||||
});
|
||||
|
||||
test('does contact server', function() {
|
||||
FindMyDevice._onRegisteredChanged(_registered);
|
||||
sinon.assert.calledOnce(contactServerSpy);
|
||||
});
|
||||
|
||||
test('does set empty current id', function() {
|
||||
FindMyDevice._onRegisteredChanged(_registered);
|
||||
sinon.assert.calledOnce(currentIdHelperSpy);
|
||||
sinon.assert.calledWith(currentIdHelperSpy, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Changes to findmydevice.enabled', function() {
|
||||
assert.isTrue(true, 'nothing to do');
|
||||
});
|
||||
|
||||
suite('Changes to findmydevice.current-clientid', function() {
|
||||
var _currentClientId = {};
|
||||
var refreshClientStub;
|
||||
|
||||
setup(function() {
|
||||
endHighPriorityStub = this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
refreshClientStub =
|
||||
this.sinon.stub(FindMyDevice, '_refreshClientIDIfRegistered');
|
||||
});
|
||||
|
||||
suite('logged and no clientid', function() {
|
||||
var oldLogged;
|
||||
|
||||
setup(function() {
|
||||
_currentClientId = { settingValue: ''};
|
||||
oldLogged = FindMyDevice._loggedIn;
|
||||
FindMyDevice._loggedIn = true;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
FindMyDevice._loggedIn = oldLogged;
|
||||
});
|
||||
|
||||
test('client id refresh', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.calledOnce(refreshClientStub);
|
||||
sinon.assert.calledWith(refreshClientStub, false);
|
||||
});
|
||||
|
||||
test('end of wakelock section', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.calledWith(endHighPriorityStub, 'clientLogic');
|
||||
});
|
||||
});
|
||||
|
||||
suite('not logged/has clientid and registered', function() {
|
||||
var oldLogged, oldRegistered, oldState;
|
||||
|
||||
setup(function() {
|
||||
_currentClientId = { settingValue: 'cid'};
|
||||
oldLogged = FindMyDevice._loggedIn;
|
||||
oldRegistered = FindMyDevice._registered;
|
||||
oldState = FindMyDevice._state;
|
||||
FindMyDevice._loggedIn = false;
|
||||
FindMyDevice._registered = true;
|
||||
FindMyDevice._state = { };
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
FindMyDevice._loggedIn = oldLogged;
|
||||
FindMyDevice._registered = oldRegistered;
|
||||
FindMyDevice._state = oldState;
|
||||
});
|
||||
|
||||
suite('calls to canDisableHelper', function() {
|
||||
var canDisableSpy;
|
||||
|
||||
setup(function() {
|
||||
canDisableSpy =
|
||||
this.sinon.spy(FindMyDevice._canDisableHelper, 'set');
|
||||
});
|
||||
|
||||
suite('with everything true', function() {
|
||||
setup(function() {
|
||||
FindMyDevice._loggedIn = true;
|
||||
FindMyDevice._state.clientid = 'cid';
|
||||
});
|
||||
|
||||
test('proper call to canDisableHelper', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.calledOnce(canDisableSpy);
|
||||
sinon.assert.calledWith(canDisableSpy, true);
|
||||
});
|
||||
});
|
||||
|
||||
suite('with loggedin false and matching clientid', function() {
|
||||
setup(function() {
|
||||
FindMyDevice._loggedIn = false;
|
||||
FindMyDevice._state.clientid = 'cid';
|
||||
});
|
||||
|
||||
test('proper call to canDisableHelper', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.calledOnce(canDisableSpy);
|
||||
sinon.assert.calledWith(canDisableSpy, false);
|
||||
});
|
||||
});
|
||||
|
||||
suite('with loggedin false and non matching clientid', function() {
|
||||
setup(function() {
|
||||
FindMyDevice._loggedIn = false;
|
||||
FindMyDevice._state.clientid = '';
|
||||
});
|
||||
|
||||
test('proper call to canDisableHelper', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.calledOnce(canDisableSpy);
|
||||
sinon.assert.calledWith(canDisableSpy, false);
|
||||
});
|
||||
});
|
||||
|
||||
suite('with loggedin true and non matching clientid', function() {
|
||||
setup(function() {
|
||||
FindMyDevice._loggedIn = true;
|
||||
FindMyDevice._state.clientid = '';
|
||||
});
|
||||
|
||||
test('proper call to canDisableHelper', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.calledOnce(canDisableSpy);
|
||||
sinon.assert.calledWith(canDisableSpy, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('keep wakelock section', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.notCalled(endHighPriorityStub);
|
||||
});
|
||||
});
|
||||
|
||||
suite('none of the above', function() {
|
||||
var oldLogged, oldRegistered;
|
||||
|
||||
setup(function() {
|
||||
_currentClientId = { settingValue: 'cid'};
|
||||
oldLogged = FindMyDevice._loggedIn;
|
||||
oldRegistered = FindMyDevice._registered;
|
||||
FindMyDevice._loggedIn = false;
|
||||
FindMyDevice._registered = false;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
FindMyDevice._loggedIn = oldLogged;
|
||||
FindMyDevice._registered = oldRegistered;
|
||||
});
|
||||
|
||||
test('end of wakelock section', function() {
|
||||
FindMyDevice._onClientIDChanged(_currentClientId);
|
||||
sinon.assert.calledWith(endHighPriorityStub, 'clientLogic');
|
||||
sinon.assert.notCalled(refreshClientStub);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Changes to findmydevice.can-disable', function() {
|
||||
var _canDisable = {};
|
||||
var enabledSpy;
|
||||
var oldDisableAttempt;
|
||||
|
||||
setup(function() {
|
||||
endHighPriorityStub = this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
enabledSpy =
|
||||
this.sinon.spy(FindMyDevice._enabledHelper, 'set');
|
||||
oldDisableAttempt = FindMyDevice._disableAttempt;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
FindMyDevice._disableAttempt = oldDisableAttempt;
|
||||
});
|
||||
|
||||
suite('can-disabled changed to true', function() {
|
||||
setup(function() {
|
||||
_canDisable = { settingValue: true};
|
||||
});
|
||||
|
||||
test('with disable attempt', function() {
|
||||
FindMyDevice._disableAttempt = true;
|
||||
FindMyDevice._onCanDisableChanged(_canDisable);
|
||||
sinon.assert.calledWith(enabledSpy, false);
|
||||
});
|
||||
|
||||
test('without disable attempt', function() {
|
||||
FindMyDevice._disableAttempt = false;
|
||||
FindMyDevice._onCanDisableChanged(_canDisable);
|
||||
sinon.assert.notCalled(enabledSpy);
|
||||
});
|
||||
|
||||
test('reset stuff', function() {
|
||||
FindMyDevice._onCanDisableChanged(_canDisable);
|
||||
assert.isFalse(FindMyDevice._disableAttempt,
|
||||
'set disableAttempt to false');
|
||||
sinon.assert.calledOnce(endHighPriorityStub, 'end wakelock section');
|
||||
});
|
||||
});
|
||||
|
||||
suite('can-disabled changed to false', function() {
|
||||
setup(function() {
|
||||
_canDisable = { settingValue: false};
|
||||
});
|
||||
|
||||
test('with disable attempt', function() {
|
||||
FindMyDevice._disableAttempt = true;
|
||||
FindMyDevice._onCanDisableChanged(_canDisable);
|
||||
sinon.assert.notCalled(enabledSpy);
|
||||
});
|
||||
|
||||
test('without disable attempt', function() {
|
||||
FindMyDevice._disableAttempt = false;
|
||||
FindMyDevice._onCanDisableChanged(_canDisable);
|
||||
sinon.assert.notCalled(enabledSpy);
|
||||
});
|
||||
|
||||
test('reset stuff', function() {
|
||||
FindMyDevice._onCanDisableChanged(_canDisable);
|
||||
assert.isFalse(FindMyDevice._disableAttempt,
|
||||
'set disableAttempt to false');
|
||||
sinon.assert.calledOnce(endHighPriorityStub, 'end wakelock section');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('findmydevice.current-clientid behavior', function() {
|
||||
setup(function() {
|
||||
this.sinon.spy(FindMyDevice._currentClientIDHelper, 'set');
|
||||
});
|
||||
|
||||
test('invalidate client id when logged in', function() {
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_LOGIN);
|
||||
assert.isTrue(FindMyDevice._loggedIn, 'not logged in after login event');
|
||||
sinon.assert.calledWith(FindMyDevice._currentClientIDHelper.set, '');
|
||||
});
|
||||
|
||||
test('invalidate client id when logged out', function() {
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_LOGOUT);
|
||||
assert.isFalse(FindMyDevice._loggedIn, 'logged in after logout event');
|
||||
sinon.assert.calledWith(FindMyDevice._currentClientIDHelper.set, '');
|
||||
});
|
||||
|
||||
test('refresh the current clientid after (re-)registering',
|
||||
function() {
|
||||
FindMyDevice._enabled = true;
|
||||
FindMyDevice._registered = true;
|
||||
this.sinon.stub(FindMyDevice, '_loadState', function(cb) { cb(); });
|
||||
FindMyDevice._onRegisteredChanged({settingValue: true});
|
||||
sinon.assert.calledWith(FindMyDevice._currentClientIDHelper.set, '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('findmydevice.can-disable behavior', function() {
|
||||
setup(function() {
|
||||
this.sinon.spy(FindMyDevice._canDisableHelper, 'set');
|
||||
});
|
||||
|
||||
test('set findmydevice.can-disable to false when logged out', function() {
|
||||
FindMyDevice._registered = true;
|
||||
FindMyDevice._loggedIn = false;
|
||||
MockNavigatorSettings.mTriggerObservers('findmydevice.current-clientid',
|
||||
{settingValue: ''});
|
||||
sinon.assert.calledWith(FindMyDevice._canDisableHelper.set, false);
|
||||
});
|
||||
|
||||
test('don\'t set findmydevice.can-disable on logout if not registered',
|
||||
function() {
|
||||
FindMyDevice._loggedIn = false;
|
||||
FindMyDevice._registered = false;
|
||||
MockNavigatorSettings.mTriggerObservers('findmydevice.current-clientid',
|
||||
{settingValue: ''});
|
||||
sinon.assert.notCalled(FindMyDevice._canDisableHelper.set);
|
||||
});
|
||||
|
||||
test('allow disabling when clientid matches the state', function() {
|
||||
FindMyDevice._loggedIn = true;
|
||||
FindMyDevice._registered = true;
|
||||
FindMyDevice._state = {clientid: 'clientid'};
|
||||
|
||||
MockNavigatorSettings.mTriggerObservers('findmydevice.current-clientid',
|
||||
{settingValue: 'clientid'});
|
||||
sinon.assert.calledWith(FindMyDevice._canDisableHelper.set, true);
|
||||
});
|
||||
|
||||
test('disallow disabling when clientid doesn\'t match the state',
|
||||
function() {
|
||||
FindMyDevice._loggedIn = true;
|
||||
FindMyDevice._registered = true;
|
||||
FindMyDevice._state = {clientid: 'wrong clientid'};
|
||||
|
||||
MockNavigatorSettings.mTriggerObservers('findmydevice.current-clientid',
|
||||
{settingValue: 'clientid'});
|
||||
sinon.assert.calledWith(FindMyDevice._canDisableHelper.set, false);
|
||||
});
|
||||
});
|
||||
|
||||
test('request client id when invalidated', function() {
|
||||
FindMyDevice._loggedIn = true;
|
||||
FindMyDevice._registered = true;
|
||||
FindMyDevice._state = {clientid: 'clientid'};
|
||||
|
||||
this.sinon.stub(FindMyDevice, 'beginHighPriority');
|
||||
this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
this.sinon.spy(FindMyDevice, '_refreshClientIDIfRegistered');
|
||||
MockNavigatorSettings.mTriggerObservers('findmydevice.current-clientid',
|
||||
{settingValue: ''});
|
||||
sinon.assert.calledWith(FindMyDevice._refreshClientIDIfRegistered, false);
|
||||
sinon.assert.calledWith(navigator.mozId.request, {});
|
||||
sinon.assert.calledOnce(FindMyDevice.beginHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.beginHighPriority, 'clientLogic');
|
||||
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'clientLogic');
|
||||
});
|
||||
|
||||
test('refresh authentication when attempting to disable', function() {
|
||||
FindMyDevice._loggedIn = true;
|
||||
FindMyDevice._registered = true;
|
||||
|
||||
this.sinon.spy(FindMyDevice, '_refreshClientIDIfRegistered');
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_TRY_DISABLE);
|
||||
assert.isTrue(FindMyDevice._disableAttempt);
|
||||
sinon.assert.calledWith(FindMyDevice._refreshClientIDIfRegistered, true);
|
||||
assert.isTrue(
|
||||
navigator.mozId.request.calledWithMatch(
|
||||
{refreshAuthentication: 0}));
|
||||
});
|
||||
|
||||
test('setting an alarm releases a wakelock', function() {
|
||||
var clock = this.sinon.useFakeTimers();
|
||||
this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
FindMyDevice._scheduleAlarm('ping');
|
||||
clock.tick();
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'clientLogic');
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test('contact the server on alarm', function() {
|
||||
this.sinon.stub(FindMyDevice, '_contactServer');
|
||||
this.sinon.stub(FindMyDevice, '_refreshClientIDIfRegistered');
|
||||
MockNavigatormozSetMessageHandler.mTrigger('alarm');
|
||||
sinon.assert.calledWith(FindMyDevice._refreshClientIDIfRegistered, false);
|
||||
sinon.assert.called(FindMyDevice._contactServer);
|
||||
});
|
||||
|
||||
test('report to the server when disabled', function() {
|
||||
FindMyDevice._registered = true;
|
||||
MockNavigatorSettings.mTriggerObservers('findmydevice.enabled',
|
||||
{settingValue: false});
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_ENABLED_CHANGED);
|
||||
sinon.assert.called(FindMyDevice._reportDisabled);
|
||||
});
|
||||
|
||||
test('track and ring are cancelled on LOCKSCREEN_CLOSED, passcode set',
|
||||
function() {
|
||||
this.sinon.stub(Commands, 'deviceHasPasscode', function() {return true;});
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED);
|
||||
sinon.assert.calledTwice(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'ring', [0]);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'track', [0]);
|
||||
});
|
||||
|
||||
test('track and ring continue on LOCKSCREEN_CLOSED, no passcode set',
|
||||
function() {
|
||||
this.sinon.stub(Commands, 'deviceHasPasscode', function() {return false;});
|
||||
sendWakeUpMessage(IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED);
|
||||
sinon.assert.notCalled(Commands.invokeCommand);
|
||||
});
|
||||
|
||||
test('wakelocks are released on unregistered clientID change', function() {
|
||||
FindMyDevice._registered = false;
|
||||
this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
MockNavigatorSettings.mTriggerObservers('findmydevice.current-clientid',
|
||||
{settingValue: ''});
|
||||
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'clientLogic');
|
||||
});
|
||||
|
||||
test('wakelocks are released when registering while already registering',
|
||||
function() {
|
||||
this.sinon.stub(FindMyDevice, 'endHighPriority');
|
||||
FindMyDevice._registering = true;
|
||||
FindMyDevice._register();
|
||||
sinon.assert.calledOnce(FindMyDevice.endHighPriority);
|
||||
sinon.assert.calledWith(FindMyDevice.endHighPriority, 'clientLogic');
|
||||
});
|
||||
|
||||
suite('testing command flow', function() {
|
||||
var serverResponse = {};
|
||||
var commandSpy;
|
||||
|
||||
setup(function() {
|
||||
FindMyDevice._enabled = true;
|
||||
commandSpy = this.sinon.spy(FindMyDevice, '_processCommands');
|
||||
});
|
||||
|
||||
suite('set alarm on server interaction', function() {
|
||||
var alarmSpy;
|
||||
|
||||
setup(function() {
|
||||
serverResponse = {t: {d: 60}};
|
||||
alarmSpy = this.sinon.spy(FindMyDevice, '_scheduleAlarm');
|
||||
});
|
||||
|
||||
suite('With FMD enabled', function() {
|
||||
setup(function() {
|
||||
FindMyDevice._handleServerResponse(serverResponse);
|
||||
});
|
||||
|
||||
test('alarm is set on successful server response', function() {
|
||||
sinon.assert.calledOnce(commandSpy);
|
||||
sinon.assert.calledWith(commandSpy, serverResponse);
|
||||
sinon.assert.calledOnce(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'track');
|
||||
sinon.assert.calledWith(alarmSpy, 'ping');
|
||||
});
|
||||
});
|
||||
|
||||
suite('With FMD disabled', function() {
|
||||
setup(function() {
|
||||
FindMyDevice._enabled = false;
|
||||
FindMyDevice._handleServerResponse(serverResponse);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
FindMyDevice._enabled = true;
|
||||
});
|
||||
|
||||
test('alarm is set on server response even when disabled', function() {
|
||||
sinon.assert.notCalled(commandSpy);
|
||||
sinon.assert.notCalled(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(alarmSpy, 'ping');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Processing of erase command', function() {
|
||||
setup(function() {
|
||||
serverResponse = {e: {}};
|
||||
FindMyDevice._handleServerResponse(serverResponse);
|
||||
});
|
||||
|
||||
test('erase command invoked', function() {
|
||||
sinon.assert.calledOnce(commandSpy);
|
||||
sinon.assert.calledWith(commandSpy, serverResponse);
|
||||
sinon.assert.calledOnce(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'erase');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Processing of killswitch command', function() {
|
||||
setup(function() {
|
||||
serverResponse = {k: {}};
|
||||
FindMyDevice._handleServerResponse(serverResponse);
|
||||
});
|
||||
|
||||
test('killswitch command invoked', function() {
|
||||
sinon.assert.calledOnce(commandSpy);
|
||||
sinon.assert.calledWith(commandSpy, serverResponse);
|
||||
sinon.assert.calledOnce(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'killswitch');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Processing of lock command', function() {
|
||||
setup(function() {
|
||||
serverResponse = {l: {m: 'message', c: '1234'}};
|
||||
FindMyDevice._handleServerResponse(serverResponse);
|
||||
});
|
||||
|
||||
test('lock command invoked', function() {
|
||||
sinon.assert.calledOnce(commandSpy);
|
||||
sinon.assert.calledWith(commandSpy, serverResponse);
|
||||
sinon.assert.calledOnce(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'lock');
|
||||
|
||||
var args = Commands.invokeCommand.firstCall.args[1];
|
||||
assert.equal(args[0], 'message');
|
||||
assert.equal(args[1], '1234');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Processing of ring command', function() {
|
||||
setup(function() {
|
||||
serverResponse = {r: {d: 5}};
|
||||
FindMyDevice._handleServerResponse(serverResponse);
|
||||
});
|
||||
|
||||
test('ring command invoked', function() {
|
||||
sinon.assert.calledOnce(commandSpy);
|
||||
sinon.assert.calledWith(commandSpy, serverResponse);
|
||||
sinon.assert.calledOnce(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'ring');
|
||||
|
||||
var args = Commands.invokeCommand.firstCall.args[1];
|
||||
assert.equal(args[0], 5);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Processing of track command', function() {
|
||||
setup(function() {
|
||||
serverResponse = {t: {d: 7}};
|
||||
FindMyDevice._handleServerResponse(serverResponse);
|
||||
});
|
||||
|
||||
test('track command invoked', function() {
|
||||
sinon.assert.calledOnce(commandSpy);
|
||||
sinon.assert.calledWith(commandSpy, serverResponse);
|
||||
sinon.assert.calledOnce(Commands.invokeCommand);
|
||||
sinon.assert.calledWith(Commands.invokeCommand, 'track');
|
||||
|
||||
var args = Commands.invokeCommand.firstCall.args[1];
|
||||
assert.equal(args[0], 7);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
3
gaia/apps/findmydevice/test/unit/setup.js
Executable file
3
gaia/apps/findmydevice/test/unit/setup.js
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mocks_helper.js');
|
||||
Loading…
Reference in a new issue