VulpesOS/gaia/apps/calendar/js/views/modify_account.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

413 lines
10 KiB
JavaScript
Executable file

define(function(require, exports, module) {
'use strict';
var Account = require('models/account');
var OAuthWindow = require('oauth_window');
var Presets = require('common/presets');
var URI = require('utils/uri');
var View = require('view');
var co = require('ext/co');
var core = require('core');
var isOffline = require('common/is_offline');
var router = require('router');
require('dom!modify-account-view');
var DEFAULT_AUTH_TYPE = 'basic';
var OAUTH_AUTH_CREDENTIALS = [
'client_id',
'scope',
'redirect_uri',
'state'
];
function ModifyAccount(options) {
View.apply(this, arguments);
this.deleteRecord = this.deleteRecord.bind(this);
this.cancel = this.cancel.bind(this);
this.displayOAuth2 = this.displayOAuth2.bind(this);
this.hideHeaderAndForm = this.hideHeaderAndForm.bind(this);
this.cancelDelete = this.cancelDelete.bind(this);
// bound so we can add remove listeners
this._boundSaveUpdateModel = this.save.bind(this, { updateModel: true });
}
module.exports = ModifyAccount;
ModifyAccount.prototype = {
__proto__: View.prototype,
_changeToken: 0,
selectors: {
element: '#modify-account-view',
form: '.modify-account-form',
fields: '*[name]',
saveButton: '#modify-account-view .save',
deleteButton: '#modify-account-view .delete-confirm',
deleteRecordButton: '.delete-record',
cancelDeleteButton: '#modify-account-view .delete-cancel',
header: '#modify-account-header',
status: '#modify-account-view section[role="status"]',
errors: '#modify-account-view .errors',
oauth2Window: '#oauth2',
oauth2SignIn: '#modify-account-view .force-oauth2'
},
progressClass: 'in-progress',
removeDialogClass: 'remove-dialog',
get authenticationType() {
if (this.preset && this.preset.authenticationType) {
return this.preset.authenticationType;
}
return DEFAULT_AUTH_TYPE;
},
get oauth2Window() {
return this._findElement('oauth2Window');
},
get oauth2SignIn() {
return this._findElement('oauth2SignIn');
},
get deleteRecordButton() {
return this._findElement('deleteRecordButton');
},
get deleteButton() {
return this._findElement('deleteButton');
},
get cancelDeleteButton() {
return this._findElement('cancelDeleteButton');
},
get header() {
return this._findElement('header');
},
get saveButton() {
return this._findElement('saveButton');
},
get form() {
return this._findElement('form');
},
get fields() {
if (!this._fields) {
var result = this._fields = {};
var elements = this.element.querySelectorAll(
this.selectors.fields
);
var i = 0;
var len = elements.length;
for (i; i < len; i++) {
var el = elements[i];
result[el.getAttribute('name')] = el;
}
}
return this._fields;
},
updateForm: function() {
var update = ['user', 'fullUrl'];
update.forEach(function(name) {
var field = this.fields[name];
field.value = this.model[name];
}, this);
},
updateModel: function() {
var update = ['user', 'password', 'fullUrl'];
update.forEach(function(name) {
var field = this.fields[name];
var value = field.value;
if (name === 'fullUrl') {
// Prepend a scheme if url has neither port nor scheme
var port = URI.getPort(value);
var scheme = URI.getScheme(value);
if (!port && !scheme) {
value = 'https://' + value;
}
}
this.model[name] = value;
}, this);
},
deleteRecord: function(e) {
if (e) {
e.preventDefault();
}
// begin the removal (which will emit the preRemove event) but don't wait
// for it to complete...
core.bridge.deleteAccount(this.model._id);
// semi-hack clear the :target - harmless in tests
// but important in the current UI because css :target
// does not get cleared (for some reason)
window.location.replace('#');
// TODO: in the future we may want to store the entry
// url of this view and use that instead of this
// hard coded value...
router.show('/advanced-settings/');
},
cancel: function(event) {
if (event) {
event.preventDefault();
}
window.history.back();
},
cancelDelete: function(event) {
this.element.classList.remove(this.removeDialogClass);
this.cancel(event);
},
isOffline: isOffline,
save: co.wrap(function *(options, e) {
if (e) {
e.preventDefault();
}
var list = this.element.classList;
if (this.isOffline()) {
this.showErrors([{name: 'offline'}]);
return;
}
list.add(this.progressClass);
this.errors.textContent = '';
if (options && options.updateModel) {
this.updateModel();
}
try {
yield core.bridge.createAccount(this.model);
list.remove(this.progressClass);
router.go(this.completeUrl);
} catch(err) {
list.remove(this.progressClass);
this.showErrors(err);
}
}),
hideHeaderAndForm: function() {
this.element.classList.add(this.removeDialogClass);
},
displayOAuth2: function(event) {
if (event) {
event.preventDefault();
}
var self = this;
this.oauth2Window.classList.add(View.ACTIVE);
navigator.mozApps.getSelf().onsuccess = function(e) {
var app = e.target.result;
app.clearBrowserData().onsuccess = function() {
self._redirectToOAuthFlow();
};
};
},
/**
* @param {String} preset name of value in Calendar.Presets.
*/
_createModel: function(preset, callback) {
var settings = Presets[preset];
var model = new Account(settings.options);
model.preset = preset;
return model;
},
_redirectToOAuthFlow: function() {
var apiCredentials = this.preset.apiCredentials;
var params = {
/*
* code response type for now might change when we can use window.open
*/
response_type: 'code',
/* offline so we get refresh_token[s] */
access_type: 'offline',
/* we us force so we always get a refresh_token */
approval_prompt: 'force'
};
OAUTH_AUTH_CREDENTIALS.forEach(function(key) {
if (key in apiCredentials) {
params[key] = apiCredentials[key];
}
});
var oauth = this._oauthDialog = new OAuthWindow(
this.oauth2Window,
apiCredentials.authorizationUrl,
params
);
var self = this;
oauth.open();
oauth.onabort = function() {
self.cancel();
};
oauth.oncomplete = function(params) {
if ('error' in params) {
// Ruh roh
return self.cancel();
}
if (!params.code) {
return console.error('authentication error');
}
// Fistpump!
self.model.oauth = { code: params.code };
self.save();
};
},
render: function() {
if (!this.model) {
throw new Error('must provider model to ModifyAccount');
}
this.form.addEventListener('submit', this._boundSaveUpdateModel);
this.saveButton.addEventListener('click', this._boundSaveUpdateModel);
this.header.addEventListener('action', this.cancel);
this.deleteRecordButton.addEventListener('click', this.hideHeaderAndForm);
if (this.model._id) {
this.type = 'update';
this.deleteButton.addEventListener('click', this.deleteRecord);
this.cancelDeleteButton.addEventListener('click', this.cancelDelete);
} else {
this.type = 'create';
}
var list = this.element.classList;
list.add(this.type);
list.add('preset-' + this.model.preset);
list.add('provider-' + this.model.providerType);
list.add('auth-' + this.authenticationType);
if (this.model.error) {
list.add('error');
}
if (this.authenticationType === 'oauth2') {
this.oauth2SignIn.addEventListener('click', this.displayOAuth2);
if (this.type === 'create') {
this.displayOAuth2();
}
this.fields.user.disabled = true;
this.saveButton.disabled = true;
}
this.form.reset();
this.updateForm();
var usernameType = this.model.usernameType;
this.fields.user.type = (usernameType === undefined) ?
'text' : usernameType;
this.header.runFontFitSoon();
},
destroy: function() {
var list = this.element.classList;
list.remove(this.type);
list.remove('preset-' + this.model.preset);
list.remove('provider-' + this.model.providerType);
list.remove('auth-' + this.authenticationType);
list.remove('error');
list.remove(this.removeDialogClass);
this.fields.user.disabled = false;
this.saveButton.disabled = false;
this._fields = null;
this.form.reset();
this.deleteRecordButton.removeEventListener('click',
this.hideHeaderAndForm);
this.oauth2SignIn.removeEventListener('click', this.displayOAuth2);
this.saveButton.removeEventListener('click', this._boundSaveUpdateModel);
this.deleteButton.removeEventListener('click', this.deleteRecord);
this.cancelDeleteButton.removeEventListener('click', this.cancelDelete);
this.header.removeEventListener('action',
this.cancel);
this.form.removeEventListener('submit', this._boundSaveUpdateModel);
},
dispatch: co.wrap(function *(data) {
if (this.model) {
this.destroy();
}
var params = data.params;
var changeToken = ++this._changeToken;
this.completeUrl = '/settings/';
try {
var model;
if (params.id) {
model = yield core.bridge.getAccount(params.id);
} else if (params.preset) {
model = this._createModel(params.preset);
}
this.preset = Presets[model.preset];
// race condition another dispatch has queued
// while we where waiting for an async event.
if (this._changeToken !== changeToken) {
return;
}
this.model = model;
this.render();
} catch (err) {
console.error('Error displaying model in ModifyAccount', data, err);
}
}),
oninactive: function() {
View.prototype.oninactive.apply(this, arguments);
if (this._oauthDialog) {
this._oauthDialog.close();
this._oauthDialog = null;
}
}
};
});