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
23
assets/webapps/findmydevice.gaiamobile.org/index.html
Normal file
23
assets/webapps/findmydevice.gaiamobile.org/index.html
Normal 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
assets/webapps/findmydevice.gaiamobile.org/js/commands.js
Normal file
220
assets/webapps/findmydevice.gaiamobile.org/js/commands.js
Normal 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();
|
||||
4
assets/webapps/findmydevice.gaiamobile.org/js/config.js
Normal file
4
assets/webapps/findmydevice.gaiamobile.org/js/config.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
Config = {
|
||||
"api_url": "https://find.firefox.com",
|
||||
"api_version": "1"
|
||||
};
|
||||
665
assets/webapps/findmydevice.gaiamobile.org/js/findmydevice.js
Normal file
665
assets/webapps/findmydevice.gaiamobile.org/js/findmydevice.js
Normal 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
assets/webapps/findmydevice.gaiamobile.org/js/lib/hawk.js
Normal file
507
assets/webapps/findmydevice.gaiamobile.org/js/lib/hawk.js
Normal 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
assets/webapps/findmydevice.gaiamobile.org/js/requester.js
Normal file
84
assets/webapps/findmydevice.gaiamobile.org/js/requester.js
Normal 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
assets/webapps/findmydevice.gaiamobile.org/jsdoc.json
Normal file
18
assets/webapps/findmydevice.gaiamobile.org/jsdoc.json
Normal 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"
|
||||
}
|
||||
}
|
||||
1
assets/webapps/findmydevice.gaiamobile.org/manifest.webapp
Executable file
1
assets/webapps/findmydevice.gaiamobile.org/manifest.webapp
Executable file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,180 @@
|
|||
/* globals indexedDB */
|
||||
/**
|
||||
* This file defines an asynchronous version of the localStorage API, backed by
|
||||
* an IndexedDB database. It creates a global asyncStorage object that has
|
||||
* methods like the localStorage object.
|
||||
*
|
||||
* To store a value use setItem:
|
||||
*
|
||||
* asyncStorage.setItem('key', 'value');
|
||||
*
|
||||
* If you want confirmation that the value has been stored, pass a callback
|
||||
* function as the third argument:
|
||||
*
|
||||
* asyncStorage.setItem('key', 'newvalue', function() {
|
||||
* console.log('new value stored');
|
||||
* });
|
||||
*
|
||||
* To read a value, call getItem(), but note that you must supply a callback
|
||||
* function that the value will be passed to asynchronously:
|
||||
*
|
||||
* asyncStorage.getItem('key', function(value) {
|
||||
* console.log('The value of key is:', value);
|
||||
* });
|
||||
*
|
||||
* Note that unlike localStorage, asyncStorage does not allow you to store and
|
||||
* retrieve values by setting and querying properties directly. You cannot just
|
||||
* write asyncStorage.key; you have to explicitly call setItem() or getItem().
|
||||
*
|
||||
* removeItem(), clear(), length(), and key() are like the same-named methods of
|
||||
* localStorage, but, like getItem() and setItem() they take a callback
|
||||
* argument.
|
||||
*
|
||||
* The asynchronous nature of getItem() makes it tricky to retrieve multiple
|
||||
* values. But unlike localStorage, asyncStorage does not require the values you
|
||||
* store to be strings. So if you need to save multiple values and want to
|
||||
* retrieve them together, in a single asynchronous operation, just group the
|
||||
* values into a single object. The properties of this object may not include
|
||||
* DOM elements, but they may include things like Blobs and typed arrays.
|
||||
*
|
||||
* Unit tests are in apps/gallery/test/unit/asyncStorage_test.js
|
||||
*/
|
||||
|
||||
this.asyncStorage = (function() {
|
||||
'use strict';
|
||||
|
||||
var DBNAME = 'asyncStorage';
|
||||
var DBVERSION = 1;
|
||||
var STORENAME = 'keyvaluepairs';
|
||||
var db = null;
|
||||
|
||||
function withDatabase(f) {
|
||||
if (db) {
|
||||
f();
|
||||
} else {
|
||||
var openreq = indexedDB.open(DBNAME, DBVERSION);
|
||||
openreq.onerror = function withStoreOnError() {
|
||||
console.error('asyncStorage: can\'t open database:',
|
||||
openreq.error.name);
|
||||
};
|
||||
openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() {
|
||||
// First time setup: create an empty object store
|
||||
openreq.result.createObjectStore(STORENAME);
|
||||
};
|
||||
openreq.onsuccess = function withStoreOnSuccess() {
|
||||
db = openreq.result;
|
||||
f();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function withStore(type, callback, oncomplete) {
|
||||
withDatabase(function() {
|
||||
var transaction = db.transaction(STORENAME, type);
|
||||
if (oncomplete) {
|
||||
transaction.oncomplete = oncomplete;
|
||||
}
|
||||
callback(transaction.objectStore(STORENAME));
|
||||
});
|
||||
}
|
||||
|
||||
function getItem(key, callback) {
|
||||
var req;
|
||||
withStore('readonly', function getItemBody(store) {
|
||||
req = store.get(key);
|
||||
req.onerror = function getItemOnError() {
|
||||
console.error('Error in asyncStorage.getItem(): ', req.error.name);
|
||||
};
|
||||
}, function onComplete() {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
callback(value);
|
||||
});
|
||||
}
|
||||
|
||||
function setItem(key, value, callback) {
|
||||
withStore('readwrite', function setItemBody(store) {
|
||||
var req = store.put(value, key);
|
||||
req.onerror = function setItemOnError() {
|
||||
console.error('Error in asyncStorage.setItem(): ', req.error.name);
|
||||
};
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function removeItem(key, callback) {
|
||||
withStore('readwrite', function removeItemBody(store) {
|
||||
var req = store.delete(key);
|
||||
req.onerror = function removeItemOnError() {
|
||||
console.error('Error in asyncStorage.removeItem(): ', req.error.name);
|
||||
};
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function clear(callback) {
|
||||
withStore('readwrite', function clearBody(store) {
|
||||
var req = store.clear();
|
||||
req.onerror = function clearOnError() {
|
||||
console.error('Error in asyncStorage.clear(): ', req.error.name);
|
||||
};
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function length(callback) {
|
||||
var req;
|
||||
withStore('readonly', function lengthBody(store) {
|
||||
req = store.count();
|
||||
req.onerror = function lengthOnError() {
|
||||
console.error('Error in asyncStorage.length(): ', req.error.name);
|
||||
};
|
||||
}, function onComplete() {
|
||||
callback(req.result);
|
||||
});
|
||||
}
|
||||
|
||||
function key(n, callback) {
|
||||
if (n < 0) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var req;
|
||||
withStore('readonly', function keyBody(store) {
|
||||
var advanced = false;
|
||||
req = store.openCursor();
|
||||
req.onsuccess = function keyOnSuccess() {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
return;
|
||||
}
|
||||
if (n === 0 || advanced) {
|
||||
// Either 1) we have the first key, return it if that's what they
|
||||
// wanted, or 2) we've got the nth key.
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, ask the cursor to skip ahead n records
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
};
|
||||
req.onerror = function keyOnError() {
|
||||
console.error('Error in asyncStorage.key(): ', req.error.name);
|
||||
};
|
||||
}, function onComplete() {
|
||||
var cursor = req.result;
|
||||
callback(cursor ? cursor.key : null);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getItem: getItem,
|
||||
setItem: setItem,
|
||||
removeItem: removeItem,
|
||||
clear: clear,
|
||||
length: length,
|
||||
key: key
|
||||
};
|
||||
}());
|
||||
|
||||
35
assets/webapps/findmydevice.gaiamobile.org/shared/js/dump.js
Normal file
35
assets/webapps/findmydevice.gaiamobile.org/shared/js/dump.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* globals dump */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function dump_off(msg, optionalObject) {}
|
||||
function dump_on(msg, optionalObject) {
|
||||
var output = msg;
|
||||
if (optionalObject) {
|
||||
try {
|
||||
output += JSON.stringify(optionalObject);
|
||||
} catch(e) {}
|
||||
}
|
||||
if (dump) {
|
||||
var appName = document.location.hostname.replace(/\..*$/, '');
|
||||
dump('[' + appName + '] ' + output + '\n');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
}
|
||||
|
||||
window.DUMP = dump_off; // no traces by default
|
||||
|
||||
// enable/disable DUMP according to the related setting
|
||||
var settings = window.navigator.mozSettings;
|
||||
var reqGaiaDebug = settings.createLock().get('debug.gaia.enabled');
|
||||
reqGaiaDebug.onsuccess = function gaiaDebug() {
|
||||
window.DUMP =
|
||||
reqGaiaDebug.result['debug.gaia.enabled'] ? dump_on : dump_off;
|
||||
};
|
||||
settings.addObserver('debug.gaia.enabled', function(event) {
|
||||
window.DUMP = event.settingValue ? dump_on : dump_off;
|
||||
dump_on(event.settingValue ? 'Enabling DUMP' : 'Disabling DUMP');
|
||||
});
|
||||
}());
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/* exported IAC_API_WAKEUP_REASON_ENABLED_CHANGED */
|
||||
/* exported IAC_API_WAKEUP_REASON_TRY_DISABLE */
|
||||
/* exported IAC_API_WAKEUP_REASON_LOGIN */
|
||||
/* exported IAC_API_WAKEUP_REASON_LOGOUT */
|
||||
/* exported IAC_API_WAKEUP_REASON_STALE_REGISTRATION */
|
||||
/* exported IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED */
|
||||
/* exported wakeUpFindMyDevice */
|
||||
|
||||
'use strict';
|
||||
|
||||
const IAC_API_WAKEUP_REASON_ENABLED_CHANGED = 0;
|
||||
const IAC_API_WAKEUP_REASON_STALE_REGISTRATION = 1;
|
||||
const IAC_API_WAKEUP_REASON_LOGIN = 2;
|
||||
const IAC_API_WAKEUP_REASON_LOGOUT = 3;
|
||||
const IAC_API_WAKEUP_REASON_TRY_DISABLE = 4;
|
||||
const IAC_API_WAKEUP_REASON_LOCKSCREEN_CLOSED = 5;
|
||||
|
||||
function wakeUpFindMyDevice(reason) {
|
||||
navigator.mozApps.getSelf().onsuccess = function() {
|
||||
var app = this.result;
|
||||
app.connect('findmydevice-wakeup').then(function(ports) {
|
||||
ports[0].postMessage(reason);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/* exported PasscodeHelper */
|
||||
/* globals crypto, TextEncoder */
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
const SET_DIGEST_VALUE = 'lockscreen.passcode-lock.digest.value';
|
||||
const SET_DIGEST_SALT = 'lockscreen.passcode-lock.digest.salt';
|
||||
const SET_DIGEST_ITERATIONS = 'lockscreen.passcode-lock.digest.iterations';
|
||||
const SET_DIGEST_ALGORITHM = 'lockscreen.passcode-lock.digest.algorithm';
|
||||
const DEFAULT_ALGORITHM = 'SHA-1'; //XXX Update to SHA-256 after bug 554827?
|
||||
const DEFAULT_ITERATIONS = 1000;
|
||||
const PBKDF2_OUTPUT_LENGTH = 160; //XXX HMAC-SHA-1 = 160. Change for SHA-256!
|
||||
|
||||
function _encode(str) {
|
||||
return new TextEncoder('utf-8').encode(str);
|
||||
}
|
||||
function _toTypedArray(obj) {
|
||||
// SettingsAPI doesnt like arrays and gives us an Object { 1: .., 2: .. }
|
||||
var a = [];
|
||||
for (var key of Object.keys(obj)) {
|
||||
a.push(obj[key]);
|
||||
}
|
||||
return new Uint8Array(a);
|
||||
}
|
||||
function _deriveBits(pwKey, salt, iterations, algorithm) {
|
||||
var params = {
|
||||
name: 'PBKDF2',
|
||||
hash: algorithm,
|
||||
salt: salt,
|
||||
iterations: iterations
|
||||
};
|
||||
return crypto.subtle.deriveBits(params, pwKey, PBKDF2_OUTPUT_LENGTH);
|
||||
}
|
||||
|
||||
function _makeDigest(pass, salt, iterations, algorithm) {
|
||||
var bytes = _encode(pass);
|
||||
var promise = crypto.subtle.importKey('raw', bytes, 'PBKDF2', false,
|
||||
['deriveBits']);
|
||||
promise = promise.then((pwKey) => {
|
||||
return _deriveBits(pwKey, salt, iterations, algorithm);
|
||||
});
|
||||
promise = promise.catch((error) => {
|
||||
console.error('PasscodeHelper: _derive_bits() failed!', error);
|
||||
throw error;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function setPasscode(newPass) {
|
||||
// Always generate a new salt.
|
||||
var salt = crypto.getRandomValues(new Uint8Array(8));
|
||||
/* the combined lock.get() makes it quite ugly. we get
|
||||
an Array of objects, each with just one key/value,
|
||||
which is the requested setting. let's destruct as follows:
|
||||
*/
|
||||
var promise = _makeDigest(newPass, salt, DEFAULT_ITERATIONS,
|
||||
DEFAULT_ALGORITHM);
|
||||
promise = promise.then((digest) => {
|
||||
return _storeNewPasscode(digest, salt);
|
||||
});
|
||||
promise = promise.catch((error) => {
|
||||
console.error('PasscodeHelper: Could not make digest:', error);
|
||||
throw error;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
function _storeNewPasscode(digest, salt) {
|
||||
// Note: We can now store the salt, since digest was generated!
|
||||
var digestUint8 = new Uint8Array(digest);
|
||||
var newSettings = {
|
||||
[SET_DIGEST_SALT]: salt,
|
||||
[SET_DIGEST_VALUE]: digestUint8,
|
||||
[SET_DIGEST_ITERATIONS]: DEFAULT_ITERATIONS,
|
||||
[SET_DIGEST_ALGORITHM]: DEFAULT_ALGORITHM,
|
||||
};
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var req = lock.set(newSettings);
|
||||
return req.then(() => {
|
||||
return digestUint8;
|
||||
}, (error) => {
|
||||
console.error('PasscodeHelper: Couldnt store new digest', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
function checkPasscode(testPass) {
|
||||
//get salt & digest out of settings
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var storedParams = Promise.all([
|
||||
lock.get(SET_DIGEST_SALT),
|
||||
lock.get(SET_DIGEST_ITERATIONS),
|
||||
lock.get(SET_DIGEST_ALGORITHM),
|
||||
lock.get(SET_DIGEST_VALUE)
|
||||
]);
|
||||
return storedParams.then((values) => {
|
||||
return _makeAndCompare(...values, testPass);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('PasscodeHelper: Couldnt get digest Settings:', error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
function _makeAndCompare(salt, iterations, algorithm, storedDigest,
|
||||
testPass) {
|
||||
/* the combined lock.get() makes it quite ugly. we get
|
||||
an Array of objects, each with just one key/value,
|
||||
which is the requested setting. let's destruct as follows:
|
||||
*/
|
||||
var _salt = _toTypedArray(salt[SET_DIGEST_SALT]);
|
||||
var _iterations = iterations[SET_DIGEST_ITERATIONS];
|
||||
var _algorithm = algorithm[SET_DIGEST_ALGORITHM];
|
||||
var _storedDigest = _toTypedArray(storedDigest[SET_DIGEST_VALUE]);
|
||||
var promise = _makeDigest(testPass, _salt, _iterations, _algorithm);
|
||||
promise = promise.then((digest) => {
|
||||
var typedDigest = new Uint8Array(digest);
|
||||
return _compareDigests(_storedDigest, typedDigest);
|
||||
});
|
||||
promise = promise.catch((error) => {
|
||||
console.error('PasscodeHelper: Couldnt create digest', error);
|
||||
throw error;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
function _compareDigests(buf1, buf2) {
|
||||
if (buf1.byteLength !== buf2.byteLength) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < buf1.byteLength; i++) {
|
||||
if (buf1[i] !== buf2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
exports.PasscodeHelper = {
|
||||
set: setPasscode,
|
||||
check: checkPasscode
|
||||
};
|
||||
})(this);
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
(function(exports) {
|
||||
'use strict';
|
||||
/**
|
||||
* SettingsHelper simplifies mozSettings access. It provides getter and setter
|
||||
* a specified setting. It iscreated by passing a setting key and an
|
||||
* optional default value.
|
||||
*
|
||||
* @param {String} key - The setting key
|
||||
* @param {Object} defaultValue - The default value
|
||||
*
|
||||
* Example:
|
||||
* // create a helper with a default false
|
||||
* var voicePrivacyHelper = SettingsHelper('ril.voicePrivacy.enabled', false);
|
||||
* // get value
|
||||
* voicePrivacyHelper.get(function(value) {});
|
||||
* // set value
|
||||
* voicePrivacyHelper.set(false, function() {});
|
||||
*/
|
||||
var SettingsHelper = function(key, defaultValue) {
|
||||
var SETTINGS_KEY = key;
|
||||
var _settings = navigator.mozSettings;
|
||||
|
||||
var _value = null;
|
||||
var _defaultValue = defaultValue;
|
||||
|
||||
var _isReady = false;
|
||||
var _callbacks = [];
|
||||
|
||||
var _return = function sh_return(callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
callback.apply(null, Array.prototype.slice.call(arguments, 1));
|
||||
};
|
||||
|
||||
var _ready = function sh_ready(callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isReady) {
|
||||
callback();
|
||||
} else {
|
||||
_callbacks.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
var _getValue = function sh_getValue(callback) {
|
||||
var req = _settings.createLock().get(SETTINGS_KEY);
|
||||
req.onsuccess = function() {
|
||||
_return(callback, req.result[SETTINGS_KEY]);
|
||||
};
|
||||
req.onerror = function() {
|
||||
console.error('Error getting ' + SETTINGS_KEY + '.');
|
||||
_return(callback, null);
|
||||
};
|
||||
};
|
||||
|
||||
var _setValue = function sh_setValue(value, callback) {
|
||||
var obj = {};
|
||||
obj[SETTINGS_KEY] = value;
|
||||
var req = _settings.createLock().set(obj);
|
||||
req.onsuccess = function() {
|
||||
_return(callback);
|
||||
};
|
||||
req.onerror = function() {
|
||||
console.error('Error setting ' + SETTINGS_KEY + '.');
|
||||
_return(callback);
|
||||
};
|
||||
};
|
||||
|
||||
var _valuechanged = function sh_valuehanged(e) {
|
||||
_value = e.settingValue;
|
||||
};
|
||||
|
||||
var _init = function sh_init(callback) {
|
||||
_getValue(function(value) {
|
||||
_value = value ? value : _defaultValue;
|
||||
_return(callback);
|
||||
});
|
||||
|
||||
_settings.addObserver(SETTINGS_KEY, _valuechanged);
|
||||
};
|
||||
|
||||
_init(function() {
|
||||
_isReady = true;
|
||||
_callbacks.forEach(function(callback) {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the setting value.
|
||||
*
|
||||
* @param {Function} callback - The setting value will be passed in the
|
||||
callback function.
|
||||
*/
|
||||
get: function(callback) {
|
||||
_ready(function() {
|
||||
_return(callback, _value ? _value : _defaultValue);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Set the setting value.
|
||||
*
|
||||
* @param {Object} value The setting value
|
||||
* @param {Function} callback The callback function.
|
||||
*/
|
||||
set: function(value, callback) {
|
||||
_ready(function() {
|
||||
_setValue(value, _return.bind(null, callback));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Cleanup ressources, specifically observers.
|
||||
*/
|
||||
uninit: function() {
|
||||
_settings.removeObserver(SETTINGS_KEY, _valuechanged);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
exports.SettingsHelper = SettingsHelper;
|
||||
})(this);
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/* exported SettingsListener */
|
||||
'use strict';
|
||||
|
||||
var SettingsListener = {
|
||||
/* lock stores here */
|
||||
_lock: null,
|
||||
|
||||
/* keep record of observers in order to remove them in the future */
|
||||
_observers: [],
|
||||
|
||||
/**
|
||||
* getSettingsLock: create a lock or retrieve one that we saved.
|
||||
* mozSettings.createLock() is expensive and lock should be reused
|
||||
* whenever possible.
|
||||
*/
|
||||
getSettingsLock: function sl_getSettingsLock() {
|
||||
// If there is a lock present we return that
|
||||
if (this._lock && !this._lock.closed) {
|
||||
return this._lock;
|
||||
}
|
||||
|
||||
// If there isn't we return one.
|
||||
var settings = window.navigator.mozSettings;
|
||||
|
||||
return (this._lock = settings.createLock());
|
||||
},
|
||||
|
||||
observe: function sl_observe(name, defaultValue, callback) {
|
||||
var settings = window.navigator.mozSettings;
|
||||
if (!settings) {
|
||||
window.setTimeout(function() { callback(defaultValue); });
|
||||
return;
|
||||
}
|
||||
|
||||
var req;
|
||||
try {
|
||||
req = this.getSettingsLock().get(name);
|
||||
} catch (e) {
|
||||
// It is possible (but rare) for getSettingsLock() to return
|
||||
// a SettingsLock object that is no longer valid.
|
||||
// Until https://bugzilla.mozilla.org/show_bug.cgi?id=793239
|
||||
// is fixed, we just catch the resulting exception and try
|
||||
// again with a fresh lock
|
||||
console.warn('Stale lock in settings_listener.js.',
|
||||
'See https://bugzilla.mozilla.org/show_bug.cgi?id=793239');
|
||||
this._lock = null;
|
||||
req = this.getSettingsLock().get(name);
|
||||
}
|
||||
|
||||
req.addEventListener('success', (function onsuccess() {
|
||||
callback(typeof(req.result[name]) != 'undefined' ?
|
||||
req.result[name] : defaultValue);
|
||||
}));
|
||||
|
||||
var settingChanged = function settingChanged(evt) {
|
||||
callback(evt.settingValue);
|
||||
};
|
||||
settings.addObserver(name, settingChanged);
|
||||
this._observers.push({
|
||||
name: name,
|
||||
callback: callback,
|
||||
observer: settingChanged
|
||||
});
|
||||
},
|
||||
|
||||
unobserve: function sl_unobserve(name, callback) {
|
||||
var settings = window.navigator.mozSettings;
|
||||
var that = this;
|
||||
this._observers.forEach(function(value, index) {
|
||||
if (value.name === name && value.callback === callback) {
|
||||
settings.removeObserver(name, value.observer);
|
||||
that._observers.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/* exported SettingsURL */
|
||||
'use strict';
|
||||
|
||||
// This library helps with managing settings that can either send back
|
||||
// string uris or blobs that need to be turned into blob: uris.
|
||||
// Usage is as follows:
|
||||
// var blobURL = new SettingsURL();
|
||||
// element.src = blobURL.set(settingValue);
|
||||
|
||||
function SettingsURL() {
|
||||
this._url = null;
|
||||
this._isBlob = false;
|
||||
}
|
||||
|
||||
SettingsURL.prototype = {
|
||||
set: function(value) {
|
||||
if (this._isBlob) {
|
||||
window.URL.revokeObjectURL(this._url);
|
||||
}
|
||||
if (value instanceof Blob) {
|
||||
this._isBlob = true;
|
||||
this._url = window.URL.createObjectURL(value);
|
||||
} else {
|
||||
this._isBlob = false;
|
||||
this._url = value;
|
||||
}
|
||||
return this._url;
|
||||
},
|
||||
|
||||
get: function() {
|
||||
return this._url;
|
||||
}
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3 KiB |
Loading…
Reference in a new issue