Merge pull request #879 from adomasven/feature/transparent-api-keygen
Restores the functionality of 4.0 for sync settings
This commit is contained in:
commit
cfee7ea9d2
13 changed files with 1076 additions and 382 deletions
|
@ -24,19 +24,232 @@
|
|||
*/
|
||||
|
||||
"use strict";
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
Zotero_Preferences.Sync = {
|
||||
init: function () {
|
||||
init: Zotero.Promise.coroutine(function* () {
|
||||
this.updateStorageSettings(null, null, true);
|
||||
|
||||
document.getElementById('sync-api-key').value = Zotero.Sync.Data.Local.getAPIKey();
|
||||
|
||||
|
||||
var username = Zotero.Users.getCurrentUsername() || "";
|
||||
var apiKey = Zotero.Sync.Data.Local.getAPIKey();
|
||||
this.displayFields(apiKey ? username : "");
|
||||
if (apiKey) {
|
||||
try {
|
||||
var keyInfo = yield Zotero.Sync.Runner.checkAccess(
|
||||
Zotero.Sync.Runner.getAPIClient({apiKey}),
|
||||
{timeout: 5000}
|
||||
);
|
||||
this.displayFields(keyInfo.username);
|
||||
}
|
||||
catch (e) {
|
||||
// API key wrong/invalid
|
||||
if (!(e instanceof Zotero.HTTP.UnexpectedStatusException) &&
|
||||
!(e instanceof Zotero.HTTP.TimeoutException)) {
|
||||
Zotero.alert(
|
||||
window,
|
||||
Zotero.getString('general.error'),
|
||||
Zotero.getString('sync.error.apiKeyInvalid', Zotero.clientName)
|
||||
);
|
||||
this.unlinkAccount(false);
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TEMP: Disabled
|
||||
//var pass = Zotero.Sync.Storage.WebDAV.password;
|
||||
//if (pass) {
|
||||
// document.getElementById('storage-password').value = pass;
|
||||
//}
|
||||
}),
|
||||
|
||||
displayFields: function (username) {
|
||||
document.getElementById('sync-unauthorized').hidden = !!username;
|
||||
document.getElementById('sync-authorized').hidden = !username;
|
||||
document.getElementById('sync-reset-tab').disabled = !username;
|
||||
document.getElementById('sync-username').value = username;
|
||||
document.getElementById('sync-password').value = '';
|
||||
document.getElementById('sync-username-textbox').value = Zotero.Prefs.get('sync.server.username');
|
||||
|
||||
var img = document.getElementById('sync-status-indicator');
|
||||
img.removeAttribute('verified');
|
||||
img.removeAttribute('animated');
|
||||
},
|
||||
|
||||
|
||||
credentialsKeyPress: function (event) {
|
||||
var username = document.getElementById('sync-username-textbox');
|
||||
username.value = username.value.trim();
|
||||
var password = document.getElementById('sync-password');
|
||||
|
||||
var syncAuthButton = document.getElementById('sync-auth-button');
|
||||
|
||||
syncAuthButton.setAttribute('disabled', 'true');
|
||||
|
||||
// When using backspace, the value is not updated until after the keypress event
|
||||
setTimeout(function() {
|
||||
if (username.value.length && password.value.length) {
|
||||
syncAuthButton.setAttribute('disabled', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
if (event.keyCode == 13) {
|
||||
Zotero_Preferences.Sync.linkAccount(event);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
linkAccount: Zotero.Promise.coroutine(function* (event) {
|
||||
var username = document.getElementById('sync-username-textbox').value;
|
||||
var password = document.getElementById('sync-password').value;
|
||||
|
||||
if (!username.length || !password.length) {
|
||||
this.updateSyncIndicator();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to acquire API key with current credentials
|
||||
this.updateSyncIndicator('animated');
|
||||
var json = yield Zotero.Sync.Runner.createAPIKeyFromCredentials(username, password);
|
||||
this.updateSyncIndicator();
|
||||
|
||||
// Invalid credentials
|
||||
if (!json) {
|
||||
Zotero.alert(window,
|
||||
Zotero.getString('general.error'),
|
||||
Zotero.getString('sync.error.invalidLogin')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(yield this.checkUser(json.userID, json.username))) {
|
||||
// createAPIKeyFromCredentials will have created an API key,
|
||||
// but user decided not to use it, so we remove it here.
|
||||
Zotero.Sync.Runner.deleteAPIKey();
|
||||
return;
|
||||
}
|
||||
|
||||
this.displayFields(json.username);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Updates the auth indicator icon, depending on status
|
||||
* @param {string} status
|
||||
*/
|
||||
updateSyncIndicator: function (status) {
|
||||
var img = document.getElementById('sync-status-indicator');
|
||||
|
||||
img.removeAttribute('animated');
|
||||
if (status == 'animated') {
|
||||
img.setAttribute('animated', true);
|
||||
}
|
||||
},
|
||||
|
||||
unlinkAccount: Zotero.Promise.coroutine(function* (showAlert=true) {
|
||||
if (showAlert) {
|
||||
if (!Services.prompt.confirm(
|
||||
null,
|
||||
Zotero.getString('general.warning'),
|
||||
Zotero.getString('sync.unlinkWarning', Zotero.clientName)
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.displayFields();
|
||||
yield Zotero.Sync.Runner.deleteAPIKey();
|
||||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Make sure we're syncing with the same account we used last time, and prompt if not.
|
||||
* If user accepts, change the current user, delete existing groups, and update relation
|
||||
* URIs to point to the new user's library.
|
||||
*
|
||||
* @param {Integer} userID New userID
|
||||
* @param {Integer} libraryID New libraryID
|
||||
* @return {Boolean} - True to continue, false to cancel
|
||||
*/
|
||||
checkUser: Zotero.Promise.coroutine(function* (userID, username) {
|
||||
var lastUserID = Zotero.Users.getCurrentUserID();
|
||||
var lastUsername = Zotero.Users.getCurrentUsername();
|
||||
|
||||
if (lastUserID && lastUserID != userID) {
|
||||
var groups = Zotero.Groups.getAll();
|
||||
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
|
||||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ ps.BUTTON_POS_1_DEFAULT
|
||||
+ ps.BUTTON_DELAY_ENABLE;
|
||||
|
||||
var msg = Zotero.getString('sync.lastSyncWithDifferentAccount', [lastUsername, username]);
|
||||
var syncButtonText = Zotero.getString('sync.sync');
|
||||
|
||||
msg += " " + Zotero.getString('sync.localDataWillBeCombined', username);
|
||||
// If there are local groups belonging to the previous user,
|
||||
// we need to remove them
|
||||
if (groups.length) {
|
||||
msg += " " + Zotero.getString('sync.localGroupsWillBeRemoved1');
|
||||
var syncButtonText = Zotero.getString('sync.removeGroupsAndSync');
|
||||
}
|
||||
msg += "\n\n" + Zotero.getString('sync.avoidCombiningData', lastUsername);
|
||||
|
||||
var index = ps.confirmEx(
|
||||
null,
|
||||
Zotero.getString('general.warning'),
|
||||
msg,
|
||||
buttonFlags,
|
||||
syncButtonText,
|
||||
null,
|
||||
Zotero.getString('sync.openSyncPreferences'),
|
||||
null, {}
|
||||
);
|
||||
|
||||
if (index > 0) {
|
||||
if (index == 2) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
lastWin.ZoteroPane.openPreferences('zotero-prefpane-sync');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
if (lastUserID != userID) {
|
||||
if (lastUserID) {
|
||||
// Delete all local groups if changing users
|
||||
for (let group of groups) {
|
||||
yield group.erase();
|
||||
}
|
||||
|
||||
// Update relations pointing to the old library to point to this one
|
||||
yield Zotero.Relations.updateUser(userID);
|
||||
}
|
||||
// Replace local user key with libraryID, in case duplicates were
|
||||
// merged before the first sync
|
||||
else {
|
||||
yield Zotero.Relations.updateUser(userID);
|
||||
}
|
||||
|
||||
yield Zotero.Users.setCurrentUserID(userID);
|
||||
}
|
||||
|
||||
if (lastUsername != username) {
|
||||
yield Zotero.Users.setCurrentUsername(username);
|
||||
}
|
||||
})
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
|
||||
updateStorageSettings: function (enabled, protocol, skipWarnings) {
|
||||
if (enabled === null) {
|
||||
|
|
|
@ -47,216 +47,240 @@
|
|||
<tabbox>
|
||||
<tabs>
|
||||
<tab label="&zotero.preferences.settings;"/>
|
||||
<tab label="&zotero.preferences.sync.reset;"/>
|
||||
<tab id="sync-reset-tab" label="&zotero.preferences.sync.reset;" disabled="true"/>
|
||||
</tabs>
|
||||
|
||||
<tabpanels>
|
||||
<tabpanel orient="vertical">
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.sync.syncServer;"/>
|
||||
|
||||
<hbox>
|
||||
<grid>
|
||||
<vbox id="sync-unauthorized">
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.sync.syncServer;"/>
|
||||
|
||||
<hbox>
|
||||
<grid>
|
||||
<columns>
|
||||
<column/>
|
||||
<column/>
|
||||
</columns>
|
||||
|
||||
<rows>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.username;"/>
|
||||
<textbox id="sync-username-textbox"
|
||||
preference="pref-sync-username"
|
||||
onkeypress="Zotero_Preferences.Sync.credentialsKeyPress(event);"/>
|
||||
</row>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.password;"/>
|
||||
<textbox id="sync-password" type="password"
|
||||
onkeypress="Zotero_Preferences.Sync.credentialsKeyPress(event);"/>
|
||||
</row>
|
||||
<vbox align="center">
|
||||
<hbox align="baseline">
|
||||
<button id="sync-auth-button"
|
||||
label="&zotero.preferences.sync.setUpSync;"
|
||||
oncommand="Zotero_Preferences.Sync.linkAccount(event)"
|
||||
disabled="true"/>
|
||||
<label id="sync-status-indicator"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</rows>
|
||||
</grid>
|
||||
<vbox style="width:2em"/>
|
||||
<vbox>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.createAccount;" href="http://zotero.org/user/register"/>
|
||||
<separator class="thin"/>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.lostPassword;" href="http://zotero.org/user/lostpassword"/>
|
||||
<separator class="thin"/>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.about;" href="http://www.zotero.org/support/sync"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
|
||||
<vbox id="sync-authorized" hidden="true">
|
||||
<groupbox>
|
||||
<caption label="&zotero.preferences.sync.syncServer;"/>
|
||||
|
||||
<hbox>
|
||||
<grid>
|
||||
<columns>
|
||||
<column/>
|
||||
<column/>
|
||||
</columns>
|
||||
|
||||
<rows>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.username;"/>
|
||||
<label id="sync-username" value="Username"/>
|
||||
</row>
|
||||
<row>
|
||||
<box/>
|
||||
<button label="&zotero.preferences.sync.unlinkAccount;"
|
||||
oncommand="Zotero_Preferences.Sync.unlinkAccount()"/>
|
||||
</row>
|
||||
<!--
|
||||
<row>
|
||||
<box/>
|
||||
<button label="Access Control" oncommand="Zotero.alert('Not implemented');"/>
|
||||
</row>
|
||||
-->
|
||||
<row>
|
||||
<box/>
|
||||
<checkbox label="&zotero.preferences.sync.syncAutomatically;"
|
||||
disabled="true"/>
|
||||
</row>
|
||||
<row>
|
||||
<box/>
|
||||
<checkbox label="&zotero.preferences.sync.syncFullTextContent;"
|
||||
tooltiptext="&zotero.preferences.sync.syncFullTextContent.desc;"
|
||||
disabled="true"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<vbox>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.about;" href="http://www.zotero.org/support/sync"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
</groupbox>
|
||||
|
||||
<groupbox id="storage-settings">
|
||||
<caption label="&zotero.preferences.sync.fileSyncing;"/>
|
||||
|
||||
<!-- My Library -->
|
||||
<hbox>
|
||||
<checkbox label="&zotero.preferences.sync.fileSyncing.myLibrary;"
|
||||
preference="pref-storage-enabled"
|
||||
oncommand="Zotero_Preferences.Sync.updateStorageSettings(this.checked, null)"/>
|
||||
<menulist id="storage-protocol" class="storage-personal"
|
||||
style="margin-left: .5em"
|
||||
preference="pref-storage-protocol"
|
||||
oncommand="Zotero_Preferences.Sync.updateStorageSettings(null, this.value)">
|
||||
<menupopup>
|
||||
<menuitem label="Zotero" value="zotero"/>
|
||||
<menuitem label="WebDAV" value="webdav" disabled="true"/><!-- TEMP -->
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<stack id="storage-webdav-settings" style="margin-top: .5em; margin-bottom: .8em; border: 1px gray solid; border-radius: 3px">
|
||||
<!-- Background shading -->
|
||||
<box style="background: black; opacity:.03"/>
|
||||
|
||||
<grid style="padding: .7em .4em .7em 0">
|
||||
<columns>
|
||||
<column/>
|
||||
<column/>
|
||||
<column flex="1"/>
|
||||
</columns>
|
||||
|
||||
|
||||
<rows>
|
||||
<!--
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.fileSyncing.url;"/>
|
||||
<hbox>
|
||||
<menulist id="storage-url-prefix"
|
||||
preference="pref-storage-scheme"
|
||||
onsynctopreference="Zotero_Preferences.Sync.unverifyStorageServer()"
|
||||
style="padding: 0; width: 7em">
|
||||
<menupopup>
|
||||
<menuitem label="https" value="https" style="padding: 0"/>
|
||||
<menuitem label="http" value="http" style="padding: 0"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
<label value="://"/>
|
||||
<textbox id="storage-url" flex="1"
|
||||
preference="pref-storage-url"
|
||||
onkeypress="if (Zotero.isMac && event.keyCode == 13) {
|
||||
this.blur();
|
||||
setTimeout(Zotero_Preferences.Sync.verifyStorageServer, 1);
|
||||
}"
|
||||
onchange="Zotero_Preferences.Sync.unverifyStorageServer();
|
||||
this.value = this.value.replace(/(^https?:\/\/|\/zotero\/?$|\/$)/g, '');
|
||||
Zotero.Prefs.set('sync.storage.url', this.value)"/>
|
||||
<label value="/zotero/"/>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.username;"/>
|
||||
<textbox preference="pref-sync-username"
|
||||
onchange="this.value = this.value.trim(); Zotero.Prefs.set('sync.server.username', this.value); var pass = document.getElementById('sync-password'); if (pass.value) { Zotero.Sync.Server.password = pass.value; }"/>
|
||||
<hbox>
|
||||
<textbox id="storage-username"
|
||||
preference="pref-storage-username"
|
||||
onkeypress="if (Zotero.isMac && event.keyCode == 13) {
|
||||
this.blur();
|
||||
setTimeout(Zotero_Preferences.Sync.verifyStorageServer, 1); }"
|
||||
onchange="Zotero_Preferences.Sync.unverifyStorageServer();
|
||||
Zotero.Prefs.set('sync.storage.username', this.value);
|
||||
var pass = document.getElementById('storage-password');
|
||||
if (pass.value) {
|
||||
Zotero.Sync.Storage.WebDAV.password = pass.value;
|
||||
}"/>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.password;"/>
|
||||
<textbox id="sync-password" type="password"
|
||||
onchange="Zotero.Sync.Server.password = this.value"/>
|
||||
<hbox>
|
||||
<textbox id="storage-password" flex="0" type="password"
|
||||
onkeypress="if (Zotero.isMac && event.keyCode == 13) {
|
||||
this.blur();
|
||||
setTimeout(Zotero_Preferences.Sync.verifyStorageServer, 1);
|
||||
}"
|
||||
onchange="Zotero_Preferences.Sync.unverifyStorageServer();
|
||||
Zotero.Sync.Storage.WebDAV.password = this.value;"/>
|
||||
</hbox>
|
||||
</row>
|
||||
-->
|
||||
<row>
|
||||
<label value="API Key (temp)"/>
|
||||
<textbox id="sync-api-key" maxlength="24" size="25"
|
||||
onchange="Zotero.Sync.Data.Local.setAPIKey(this.value)"/>
|
||||
</row>
|
||||
<row>
|
||||
<box/>
|
||||
<!--<checkbox label="&zotero.preferences.sync.syncAutomatically;" preference="pref-sync-autosync"/>-->
|
||||
<checkbox label="&zotero.preferences.sync.syncAutomatically;"
|
||||
disabled="true"/>
|
||||
</row>
|
||||
<row>
|
||||
<box/>
|
||||
<vbox>
|
||||
<!--<checkbox label="&zotero.preferences.sync.syncFullTextContent;"
|
||||
preference="pref-sync-fulltext-enabled"
|
||||
tooltiptext="&zotero.preferences.sync.syncFullTextContent.desc;"/>-->
|
||||
<checkbox label="&zotero.preferences.sync.syncFullTextContent;"
|
||||
tooltiptext="&zotero.preferences.sync.syncFullTextContent.desc;"
|
||||
disabled="true"/>
|
||||
</vbox>
|
||||
</row>
|
||||
<!--
|
||||
<row>
|
||||
<box/>
|
||||
<hbox>
|
||||
<button label="Verify login"
|
||||
oncommand="alert('Unimplemented')"/>
|
||||
<button id="storage-verify" label="Verify Server"
|
||||
oncommand="Zotero_Preferences.Sync.verifyStorageServer()"/>
|
||||
<button id="storage-abort" label="Stop" hidden="true"/>
|
||||
<progressmeter id="storage-progress" hidden="true"
|
||||
mode="undetermined"/>
|
||||
</hbox>
|
||||
</row>
|
||||
-->
|
||||
</rows>
|
||||
</grid>
|
||||
|
||||
<hbox style="width:2em"/>
|
||||
|
||||
<vbox>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.about;" href="http://www.zotero.org/support/sync"/>
|
||||
<separator class="thin"/>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.createAccount;" href="http://zotero.org/user/register"/>
|
||||
<separator class="thin"/>
|
||||
<label class="zotero-text-link" value="&zotero.preferences.sync.lostPassword;" href="http://zotero.org/user/lostpassword"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
|
||||
|
||||
<groupbox id="storage-settings">
|
||||
<caption label="&zotero.preferences.sync.fileSyncing;"/>
|
||||
|
||||
<!-- My Library -->
|
||||
<hbox>
|
||||
<checkbox label="&zotero.preferences.sync.fileSyncing.myLibrary;"
|
||||
preference="pref-storage-enabled"
|
||||
oncommand="Zotero_Preferences.Sync.updateStorageSettings(this.checked, null)"/>
|
||||
<menulist id="storage-protocol" class="storage-personal"
|
||||
style="margin-left: .5em"
|
||||
preference="pref-storage-protocol"
|
||||
oncommand="Zotero_Preferences.Sync.updateStorageSettings(null, this.value)">
|
||||
<menupopup>
|
||||
<menuitem label="Zotero" value="zotero"/>
|
||||
<menuitem label="WebDAV" value="webdav" disabled="true"/><!-- TEMP -->
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<stack id="storage-webdav-settings" style="margin-top: .5em; margin-bottom: .8em; border: 1px gray solid; border-radius: 3px">
|
||||
<!-- Background shading -->
|
||||
<box style="background: black; opacity:.03"/>
|
||||
|
||||
<grid style="padding: .7em .4em .7em 0">
|
||||
<columns>
|
||||
<column/>
|
||||
<column flex="1"/>
|
||||
</columns>
|
||||
|
||||
<rows>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.fileSyncing.url;"/>
|
||||
<hbox>
|
||||
<menulist id="storage-url-prefix"
|
||||
preference="pref-storage-scheme"
|
||||
onsynctopreference="Zotero_Preferences.Sync.unverifyStorageServer()"
|
||||
style="padding: 0; width: 7em">
|
||||
<menupopup>
|
||||
<menuitem label="https" value="https" style="padding: 0"/>
|
||||
<menuitem label="http" value="http" style="padding: 0"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
<label value="://"/>
|
||||
<textbox id="storage-url" flex="1"
|
||||
preference="pref-storage-url"
|
||||
onkeypress="if (Zotero.isMac && event.keyCode == 13) {
|
||||
this.blur();
|
||||
setTimeout(Zotero_Preferences.Sync.verifyStorageServer, 1);
|
||||
}"
|
||||
onchange="Zotero_Preferences.Sync.unverifyStorageServer();
|
||||
this.value = this.value.replace(/(^https?:\/\/|\/zotero\/?$|\/$)/g, '');
|
||||
Zotero.Prefs.set('sync.storage.url', this.value)"/>
|
||||
<label value="/zotero/"/>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.username;"/>
|
||||
<hbox>
|
||||
<textbox id="storage-username"
|
||||
preference="pref-storage-username"
|
||||
onkeypress="if (Zotero.isMac && event.keyCode == 13) {
|
||||
this.blur();
|
||||
setTimeout(Zotero_Preferences.Sync.verifyStorageServer, 1); }"
|
||||
onchange="Zotero_Preferences.Sync.unverifyStorageServer();
|
||||
Zotero.Prefs.set('sync.storage.username', this.value);
|
||||
var pass = document.getElementById('storage-password');
|
||||
if (pass.value) {
|
||||
Zotero.Sync.Storage.WebDAV.password = pass.value;
|
||||
}"/>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
<label value="&zotero.preferences.sync.password;"/>
|
||||
<hbox>
|
||||
<textbox id="storage-password" flex="0" type="password"
|
||||
onkeypress="if (Zotero.isMac && event.keyCode == 13) {
|
||||
this.blur();
|
||||
setTimeout(Zotero_Preferences.Sync.verifyStorageServer, 1);
|
||||
}"
|
||||
onchange="Zotero_Preferences.Sync.unverifyStorageServer();
|
||||
Zotero.Sync.Storage.WebDAV.password = this.value;"/>
|
||||
</hbox>
|
||||
</row>
|
||||
<row>
|
||||
<box/>
|
||||
<hbox>
|
||||
<button id="storage-verify" label="Verify Server"
|
||||
oncommand="Zotero_Preferences.Sync.verifyStorageServer()"/>
|
||||
<button id="storage-abort" label="Stop" hidden="true"/>
|
||||
<progressmeter id="storage-progress" hidden="true"
|
||||
mode="undetermined"/>
|
||||
</hbox>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
|
||||
</stack>
|
||||
|
||||
<hbox class="storage-settings-download-options" align="center">
|
||||
<label value="&zotero.preferences.sync.fileSyncing.download;"/>
|
||||
<menulist class="storage-personal" preference="pref-storage-downloadMode-personal" style="margin-left: 0">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.onDemand;" value="on-demand"/>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.atSyncTime;" value="on-sync"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<separator id="storage-separator" class="thin"/>
|
||||
|
||||
<!-- Group Libraries -->
|
||||
<checkbox label="&zotero.preferences.sync.fileSyncing.groups;"
|
||||
preference="pref-group-storage-enabled"
|
||||
oncommand="Zotero_Preferences.Sync.updateStorageSettingsGroups(this.checked)"/>
|
||||
|
||||
<hbox class="storage-settings-download-options" align="center">
|
||||
<label value="&zotero.preferences.sync.fileSyncing.download;"/>
|
||||
<menulist class="storage-groups" preference="pref-storage-downloadMode-groups" style="margin-left: 0">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.onDemand;" value="on-demand"/>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.atSyncTime;" value="on-sync"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<vbox>
|
||||
<hbox id="storage-terms" style="margin-top: .4em; display: block" align="center">
|
||||
<label>&zotero.preferences.sync.fileSyncing.tos1;</label>
|
||||
<label class="zotero-text-link" href="https://www.zotero.org/support/terms/terms_of_service" value="&zotero.preferences.sync.fileSyncing.tos2;"/>
|
||||
<label>&zotero.preferences.period;</label>
|
||||
|
||||
</stack>
|
||||
|
||||
<hbox class="storage-settings-download-options" align="center">
|
||||
<label value="&zotero.preferences.sync.fileSyncing.download;"/>
|
||||
<menulist class="storage-personal" preference="pref-storage-downloadMode-personal" style="margin-left: 0">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.onDemand;" value="on-demand"/>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.atSyncTime;" value="on-sync"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
|
||||
<separator id="storage-separator" class="thin"/>
|
||||
|
||||
<!-- Group Libraries -->
|
||||
<checkbox label="&zotero.preferences.sync.fileSyncing.groups;"
|
||||
preference="pref-group-storage-enabled"
|
||||
oncommand="Zotero_Preferences.Sync.updateStorageSettingsGroups(this.checked)"/>
|
||||
|
||||
<hbox class="storage-settings-download-options" align="center">
|
||||
<label value="&zotero.preferences.sync.fileSyncing.download;"/>
|
||||
<menulist class="storage-groups" preference="pref-storage-downloadMode-groups" style="margin-left: 0">
|
||||
<menupopup>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.onDemand;" value="on-demand"/>
|
||||
<menuitem label="&zotero.preferences.sync.fileSyncing.download.atSyncTime;" value="on-sync"/>
|
||||
</menupopup>
|
||||
</menulist>
|
||||
</hbox>
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<vbox>
|
||||
<hbox id="storage-terms" style="margin-top: .4em; display: block" align="center">
|
||||
<label>&zotero.preferences.sync.fileSyncing.tos1;</label>
|
||||
<label class="zotero-text-link" href="https://www.zotero.org/support/terms/terms_of_service" value="&zotero.preferences.sync.fileSyncing.tos2;"/>
|
||||
<label>&zotero.preferences.period;</label>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</groupbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
|
||||
<tabpanel id="zotero-reset" orient="vertical">
|
||||
|
|
|
@ -55,7 +55,15 @@ Zotero.HTTP = new function() {
|
|||
this.BrowserOfflineException.prototype.toString = function() {
|
||||
return this.message;
|
||||
};
|
||||
|
||||
|
||||
this.TimeoutException = function(ms) {
|
||||
this.message = "XMLHttpRequest has timed out after " + ms + "ms";
|
||||
};
|
||||
this.TimeoutException.prototype = Object.create(Error.prototype);
|
||||
this.TimeoutException.prototype.toString = function() {
|
||||
return this.message;
|
||||
};
|
||||
|
||||
this.promise = function () {
|
||||
Zotero.debug("Zotero.HTTP.promise() is deprecated -- use Zotero.HTTP.request()", 2);
|
||||
return this.request.apply(this, arguments);
|
||||
|
@ -74,6 +82,7 @@ Zotero.HTTP = new function() {
|
|||
* <li>dontCache - If set, specifies that the request should not be fulfilled from the cache</li>
|
||||
* <li>foreground - Make a foreground request, showing certificate/authentication dialogs if necessary</li>
|
||||
* <li>headers - HTTP headers to include in the request</li>
|
||||
* <li>timeout - Request timeout specified in milliseconds
|
||||
* <li>requestObserver - Callback to receive XMLHttpRequest after open()</li>
|
||||
* <li>responseType - The type of the response. See XHR 2 documentation for legal values</li>
|
||||
* <li>responseCharset - The charset the response should be interpreted as</li>
|
||||
|
@ -191,7 +200,16 @@ Zotero.HTTP = new function() {
|
|||
for (var header in headers) {
|
||||
xmlhttp.setRequestHeader(header, headers[header]);
|
||||
}
|
||||
|
||||
|
||||
// Set timeout
|
||||
if (options.timeout) {
|
||||
xmlhttp.timeout = options.timeout;
|
||||
}
|
||||
|
||||
xmlhttp.ontimeout = function() {
|
||||
deferred.reject(new Zotero.HTTP.TimeoutException(options.timeout));
|
||||
};
|
||||
|
||||
xmlhttp.onloadend = function() {
|
||||
var status = xmlhttp.status;
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ if (!Zotero.Sync) {
|
|||
Zotero.Sync.APIClient = function (options) {
|
||||
if (!options.baseURL) throw new Error("baseURL not set");
|
||||
if (!options.apiVersion) throw new Error("apiVersion not set");
|
||||
if (!options.apiKey) throw new Error("apiKey not set");
|
||||
if (!options.caller) throw new Error("caller not set");
|
||||
|
||||
this.baseURL = options.baseURL;
|
||||
|
@ -45,9 +44,9 @@ Zotero.Sync.APIClient.prototype = {
|
|||
MAX_OBJECTS_PER_REQUEST: 100,
|
||||
|
||||
|
||||
getKeyInfo: Zotero.Promise.coroutine(function* () {
|
||||
getKeyInfo: Zotero.Promise.coroutine(function* (options={}) {
|
||||
var uri = this.baseURL + "keys/" + this.apiKey;
|
||||
var xmlhttp = yield this.makeRequest("GET", uri, { successCodes: [200, 404] });
|
||||
var xmlhttp = yield this.makeRequest("GET", uri, Object.assign(options, { successCodes: [200, 404] }));
|
||||
if (xmlhttp.status == 404) {
|
||||
return false;
|
||||
}
|
||||
|
@ -430,6 +429,52 @@ Zotero.Sync.APIClient.prototype = {
|
|||
}),
|
||||
|
||||
|
||||
createAPIKeyFromCredentials: Zotero.Promise.coroutine(function* (username, password) {
|
||||
var body = JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
name: "Automatic Zotero Client Key",
|
||||
access: {
|
||||
user: {
|
||||
library: true,
|
||||
notes: true,
|
||||
write: true,
|
||||
files: true
|
||||
},
|
||||
groups: {
|
||||
all: {
|
||||
library: true,
|
||||
write: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var headers = {
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
var uri = this.baseURL + "keys";
|
||||
var response = yield this.makeRequest("POST", uri, {
|
||||
body, headers, successCodes: [201, 403], noAPIKey: true
|
||||
});
|
||||
if (response.status == 403) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var json = this._parseJSON(response.responseText);
|
||||
if (!json.key) {
|
||||
throw new Error('json.key not present in POST /keys response')
|
||||
}
|
||||
|
||||
return json;
|
||||
}),
|
||||
|
||||
|
||||
// Deletes current API key
|
||||
deleteAPIKey: Zotero.Promise.coroutine(function* () {
|
||||
yield this.makeRequest("DELETE", this.baseURL + "keys/" + this.apiKey);
|
||||
}),
|
||||
|
||||
|
||||
buildRequestURI: function (params) {
|
||||
var uri = this.baseURL;
|
||||
|
||||
|
@ -508,6 +553,9 @@ Zotero.Sync.APIClient.prototype = {
|
|||
|
||||
|
||||
makeRequest: Zotero.Promise.coroutine(function* (method, uri, options = {}) {
|
||||
if (!this.apiKey && !options.noAPIKey) {
|
||||
throw new Error('API key not set');
|
||||
}
|
||||
options.headers = this.getHeaders(options.headers);
|
||||
options.dontCache = true;
|
||||
options.foreground = !options.background;
|
||||
|
|
|
@ -55,9 +55,11 @@ Zotero.Sync.Data.Local = {
|
|||
var oldLoginInfo = this._getAPIKeyLoginInfo();
|
||||
|
||||
// Clear old login
|
||||
if (oldLoginInfo && (!apiKey || apiKey === "")) {
|
||||
Zotero.debug("Clearing old API key");
|
||||
loginManager.removeLogin(oldLoginInfo);
|
||||
if ((!apiKey || apiKey === "")) {
|
||||
if (oldLoginInfo) {
|
||||
Zotero.debug("Clearing old API key");
|
||||
loginManager.removeLogin(oldLoginInfo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -154,6 +156,31 @@ Zotero.Sync.Data.Local = {
|
|||
},
|
||||
|
||||
|
||||
removeLegacyLogins: function () {
|
||||
var loginManagerHost = 'chrome://zotero';
|
||||
var loginManagerRealm = 'Zotero Sync Server';
|
||||
|
||||
Zotero.debug('Removing legacy Zotero sync credentials (api key acquired)');
|
||||
|
||||
var loginManager = Components.classes["@mozilla.org/login-manager;1"]
|
||||
.getService(Components.interfaces.nsILoginManager);
|
||||
try {
|
||||
var logins = loginManager.findLogins({}, loginManagerHost, null, loginManagerRealm);
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.logError(e);
|
||||
return '';
|
||||
}
|
||||
|
||||
// Remove all legacy users
|
||||
for (let login of logins) {
|
||||
loginManager.removeLogin(login);
|
||||
}
|
||||
// Remove the legacy pref
|
||||
Zotero.Pref.clear('sync.server.username');
|
||||
},
|
||||
|
||||
|
||||
getLastSyncTime: function () {
|
||||
if (_lastSyncTime === null) {
|
||||
throw new Error("Last sync time not yet loaded");
|
||||
|
|
|
@ -68,12 +68,9 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
var _currentLastSyncLabel;
|
||||
var _errors = [];
|
||||
|
||||
|
||||
|
||||
this.getAPIClient = function (options = {}) {
|
||||
if (!options.apiKey) {
|
||||
throw new Error("apiKey not provided");
|
||||
}
|
||||
|
||||
|
||||
return new Zotero.Sync.APIClient({
|
||||
baseURL: this.baseURL,
|
||||
apiVersion: this.apiVersion,
|
||||
|
@ -218,8 +215,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
/**
|
||||
* Check key for current user info and return access info
|
||||
*/
|
||||
this.checkAccess = Zotero.Promise.coroutine(function* (client, options) {
|
||||
var json = yield client.getKeyInfo();
|
||||
this.checkAccess = Zotero.Promise.coroutine(function* (client, options={}) {
|
||||
var json = yield client.getKeyInfo(options);
|
||||
Zotero.debug(json);
|
||||
if (!json) {
|
||||
// TODO: Nicer error message
|
||||
|
@ -231,11 +228,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
if (!json.username) throw new Error("username not found in key response");
|
||||
if (!json.access) throw new Error("'access' not found in key response");
|
||||
|
||||
// Make sure user hasn't changed, and prompt to update database if so
|
||||
if (!(yield this.checkUser(json.userID, json.username))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return json;
|
||||
});
|
||||
|
||||
|
@ -425,108 +417,6 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Make sure we're syncing with the same account we used last time, and prompt if not.
|
||||
* If user accepts, change the current user, delete existing groups, and update relation
|
||||
* URIs to point to the new user's library.
|
||||
*
|
||||
* @param {Integer} userID New userID
|
||||
* @param {Integer} libraryID New libraryID
|
||||
* @return {Boolean} - True to continue, false to cancel
|
||||
*/
|
||||
this.checkUser = Zotero.Promise.coroutine(function* (userID, username) {
|
||||
var lastUserID = Zotero.Users.getCurrentUserID();
|
||||
var lastUsername = Zotero.Users.getCurrentUsername();
|
||||
|
||||
// TEMP: Remove? No way to determine this quickly currently.
|
||||
var noServerData = false;
|
||||
|
||||
if (lastUserID && lastUserID != userID) {
|
||||
var groups = Zotero.Groups.getAll();
|
||||
|
||||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Components.interfaces.nsIPromptService);
|
||||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL)
|
||||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING)
|
||||
+ ps.BUTTON_POS_1_DEFAULT
|
||||
+ ps.BUTTON_DELAY_ENABLE;
|
||||
|
||||
var msg = Zotero.getString('sync.lastSyncWithDifferentAccount', [lastUsername, username]);
|
||||
|
||||
if (!noServerData) {
|
||||
msg += " " + Zotero.getString('sync.localDataWillBeCombined', username);
|
||||
// If there are local groups belonging to the previous user,
|
||||
// we need to remove them
|
||||
if (groups.length) {
|
||||
msg += " " + Zotero.getString('sync.localGroupsWillBeRemoved1');
|
||||
}
|
||||
msg += "\n\n" + Zotero.getString('sync.avoidCombiningData', lastUsername);
|
||||
var syncButtonText = Zotero.getString('sync.sync');
|
||||
}
|
||||
else if (groups.length) {
|
||||
msg += " " + Zotero.getString('sync.localGroupsWillBeRemoved2', [username, lastUsername]);
|
||||
var syncButtonText = Zotero.getString('sync.removeGroupsAndSync');
|
||||
}
|
||||
// If there are no local groups and the server is empty,
|
||||
// don't bother prompting
|
||||
else {
|
||||
var noPrompt = true;
|
||||
}
|
||||
|
||||
if (!noPrompt) {
|
||||
var index = ps.confirmEx(
|
||||
null,
|
||||
Zotero.getString('general.warning'),
|
||||
msg,
|
||||
buttonFlags,
|
||||
syncButtonText,
|
||||
null,
|
||||
Zotero.getString('sync.openSyncPreferences'),
|
||||
null, {}
|
||||
);
|
||||
|
||||
if (index > 0) {
|
||||
if (index == 2) {
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var lastWin = wm.getMostRecentWindow("navigator:browser");
|
||||
lastWin.ZoteroPane.openPreferences('zotero-prefpane-sync');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
if (lastUserID != userID) {
|
||||
if (lastUserID) {
|
||||
// Delete all local groups if changing users
|
||||
for (let group of groups) {
|
||||
yield group.erase();
|
||||
}
|
||||
|
||||
// Update relations pointing to the old library to point to this one
|
||||
yield Zotero.Relations.updateUser(userID);
|
||||
}
|
||||
// Replace local user key with libraryID, in case duplicates were
|
||||
// merged before the first sync
|
||||
else {
|
||||
yield Zotero.Relations.updateUser(userID);
|
||||
}
|
||||
|
||||
yield Zotero.Users.setCurrentUserID(userID);
|
||||
}
|
||||
|
||||
if (lastUsername != username) {
|
||||
yield Zotero.Users.setCurrentUsername(username);
|
||||
}
|
||||
})
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Run sync engine for passed libraries
|
||||
*
|
||||
|
@ -1191,7 +1081,33 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
_updateSyncStatusLabel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.createAPIKeyFromCredentials = Zotero.Promise.coroutine(function* (username, password) {
|
||||
var client = this.getAPIClient();
|
||||
var json = yield client.createAPIKeyFromCredentials(username, password);
|
||||
if (!json) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!json.userID) throw new Error("userID not found in key response");
|
||||
if (!json.username) throw new Error("username not found in key response");
|
||||
if (!json.access) throw new Error("'access' not found in key response");
|
||||
|
||||
Zotero.Sync.Data.Local.setAPIKey(json.key);
|
||||
|
||||
return json;
|
||||
})
|
||||
|
||||
|
||||
this.deleteAPIKey = Zotero.Promise.coroutine(function* (){
|
||||
var apiKey = Zotero.Sync.Data.Local.getAPIKey();
|
||||
var client = this.getAPIClient({apiKey});
|
||||
Zotero.Sync.Data.Local.setAPIKey();
|
||||
yield client.deleteAPIKey();
|
||||
})
|
||||
|
||||
|
||||
function _updateSyncStatusLabel() {
|
||||
if (_lastSyncStatus) {
|
||||
|
@ -1244,20 +1160,20 @@ Zotero.Sync.Runner_Module = function (options = {}) {
|
|||
|
||||
|
||||
var _getAPIKeyFromLogin = Zotero.Promise.coroutine(function* () {
|
||||
var apiKey = "";
|
||||
var apiKey;
|
||||
let username = Zotero.Prefs.get('sync.server.username');
|
||||
if (username) {
|
||||
// Check for legacy password if no password set in current session
|
||||
// and no API keys stored yet
|
||||
let password = Zotero.Sync.Data.Local.getLegacyPassword(username);
|
||||
if (!password) {
|
||||
return "";
|
||||
}
|
||||
throw new Error("Unimplemented");
|
||||
// Get API key from server
|
||||
|
||||
// Store API key
|
||||
|
||||
// Remove old logins and username pref
|
||||
|
||||
apiKey = yield Zotero.Sync.Runner.createAPIKeyFromCredentials(username, password);
|
||||
Zotero.Sync.Data.Local.removeLegacyLogins();
|
||||
return apiKey;
|
||||
}
|
||||
return apiKey;
|
||||
return "";
|
||||
})
|
||||
}
|
||||
|
|
|
@ -49,7 +49,9 @@
|
|||
<!ENTITY zotero.preferences.prefpane.sync "Sync">
|
||||
<!ENTITY zotero.preferences.sync.username "Username:">
|
||||
<!ENTITY zotero.preferences.sync.password "Password:">
|
||||
<!ENTITY zotero.preferences.sync.syncServer "Zotero Sync Server">
|
||||
<!ENTITY zotero.preferences.sync.syncServer "Zotero Data Sync">
|
||||
<!ENTITY zotero.preferences.sync.setUpSync "Set Up Syncing">
|
||||
<!ENTITY zotero.preferences.sync.unlinkAccount "Unlink Account...">
|
||||
<!ENTITY zotero.preferences.sync.createAccount "Create Account">
|
||||
<!ENTITY zotero.preferences.sync.lostPassword "Lost Password?">
|
||||
<!ENTITY zotero.preferences.sync.syncAutomatically "Sync automatically">
|
||||
|
|
|
@ -821,12 +821,13 @@ sync.error.sslConnectionError = SSL connection error
|
|||
sync.error.checkConnection = Error connecting to server. Check your Internet connection.
|
||||
sync.error.emptyResponseServer = Empty response from server.
|
||||
sync.error.invalidCharsFilename = The filename '%S' contains invalid characters.\n\nRename the file and try again. If you rename the file via the OS, you will need to relink it in Zotero.
|
||||
sync.error.apiKeyInvalid = %S could not authenticate your account. Please re-enter your account details.
|
||||
|
||||
sync.lastSyncWithDifferentAccount = This Zotero database was last synced with a different zotero.org account ('%1$S') from the current one ('%2$S').
|
||||
sync.localDataWillBeCombined = If you continue, local Zotero data will be combined with data from the '%S' account stored on the server.
|
||||
sync.localGroupsWillBeRemoved1 = Local groups, including any with changed items, will also be removed from this computer.
|
||||
sync.avoidCombiningData = To avoid combining or losing data, revert to the '%S' account or use the Reset options in the Sync pane of the Zotero preferences.
|
||||
sync.localGroupsWillBeRemoved2 = If you continue, local groups, including any with changed items, will be removed and replaced with groups linked to the '%1$S' account.\n\nTo avoid losing local changes to groups, be sure you have synced with the '%2$S' account before syncing with the '%1$S' account.
|
||||
sync.avoidCombiningData = To avoid combining data, revert to the '%S' account or use the Reset options in the Sync pane of the Zotero preferences.
|
||||
sync.unlinkWarning = Are you sure you want to unlink this account?\n\n%S will no longer sync your data, but your data will remain locally.
|
||||
|
||||
sync.conflict.autoChange.alert = One or more locally deleted Zotero %S have been modified remotely since the last sync.
|
||||
sync.conflict.autoChange.log = A Zotero %S has changed both locally and remotely since the last sync:
|
||||
|
|
|
@ -118,6 +118,30 @@ grid row hbox:first-child
|
|||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#zotero-prefpane-sync #sync-status-indicator
|
||||
{
|
||||
width: 1.5em;
|
||||
height: 1.7em;
|
||||
margin-top: 0.4em;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
#zotero-prefpane-sync #sync-status-indicator[verified=true]
|
||||
{
|
||||
background-image: url("chrome://zotero/skin/tick.png")
|
||||
}
|
||||
|
||||
#zotero-prefpane-sync #sync-status-indicator[verified=false]
|
||||
{
|
||||
background-image: url("chrome://zotero/skin/cross.png")
|
||||
}
|
||||
|
||||
#zotero-prefpane-sync #sync-status-indicator[animated]
|
||||
{
|
||||
background-image: url("chrome://zotero/skin/arrow_rotate_animated.png")
|
||||
}
|
||||
|
||||
.storage-settings-download-options
|
||||
{
|
||||
margin-left: 40px;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<script src="resource://zotero-unit/chai-as-promised/lib/chai-as-promised.js"></script>
|
||||
<script src="resource://zotero-unit/mocha/mocha.js"></script>
|
||||
<script src="resource://zotero-unit/sinon.js"></script>
|
||||
<script src="resource://zotero-unit/sinon-as-promised.js"></script>
|
||||
<script src="support.js" type="application/javascript;version=1.8"></script>
|
||||
<script src="runtests.js" type="application/javascript;version=1.8"></script>
|
||||
</body>
|
||||
|
|
250
test/resource/sinon-as-promised.js
Normal file
250
test/resource/sinon-as-promised.js
Normal file
|
@ -0,0 +1,250 @@
|
|||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.sinonAsPromised = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
'use strict'
|
||||
|
||||
var Promise = window.Zotero.Promise
|
||||
var sinon = (window.sinon)
|
||||
|
||||
function methods (Promise) {
|
||||
return ['catch', 'finally'].concat(Object.keys(Promise.prototype)).filter(a => a != 'then');
|
||||
}
|
||||
function createThenable (Promise, resolver) {
|
||||
return methods(Promise).reduce(createMethod, {then: then})
|
||||
function createMethod (thenable, name) {
|
||||
thenable[name] = method(name)
|
||||
return thenable
|
||||
}
|
||||
function method (name) {
|
||||
return function () {
|
||||
var promise = this.then()
|
||||
return promise[name].apply(promise, arguments)
|
||||
}
|
||||
}
|
||||
function then (/*onFulfill, onReject*/) {
|
||||
var promise = new Promise(resolver)
|
||||
return promise.then.apply(promise, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
function resolves (value) {
|
||||
return this.returns(createThenable(Promise, function (resolve) {
|
||||
resolve(value)
|
||||
}))
|
||||
}
|
||||
|
||||
sinon.stub.resolves = resolves
|
||||
sinon.behavior.resolves = resolves
|
||||
|
||||
function rejects (err) {
|
||||
if (typeof err === 'string') {
|
||||
err = new Error(err)
|
||||
}
|
||||
return this.returns(createThenable(Promise, function (resolve, reject) {
|
||||
reject(err)
|
||||
}))
|
||||
}
|
||||
|
||||
sinon.stub.rejects = rejects
|
||||
sinon.behavior.rejects = rejects
|
||||
|
||||
module.exports = function (_Promise_) {
|
||||
if (typeof _Promise_ !== 'function') {
|
||||
throw new Error('A Promise constructor must be provided')
|
||||
} else {
|
||||
Promise = _Promise_
|
||||
}
|
||||
return sinon
|
||||
}
|
||||
|
||||
},{"create-thenable":7,"native-promise-only":8}],2:[function(require,module,exports){
|
||||
/*!
|
||||
* object.omit <https://github.com/jonschlinkert/object.omit>
|
||||
*
|
||||
* Copyright (c) 2014-2015 Jon Schlinkert.
|
||||
* Licensed under the MIT License
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var isObject = require('isobject');
|
||||
var forOwn = require('for-own');
|
||||
|
||||
module.exports = function omit(obj, props) {
|
||||
if (obj == null || !isObject(obj)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (props == null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (typeof props === 'string') {
|
||||
props = [].slice.call(arguments, 1);
|
||||
}
|
||||
|
||||
var o = {};
|
||||
|
||||
if (!Object.keys(obj).length) {
|
||||
return o;
|
||||
}
|
||||
|
||||
forOwn(obj, function (value, key) {
|
||||
if (props.indexOf(key) === -1) {
|
||||
o[key] = value;
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
},{"for-own":3,"isobject":5}],3:[function(require,module,exports){
|
||||
/*!
|
||||
* for-own <https://github.com/jonschlinkert/for-own>
|
||||
*
|
||||
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var forIn = require('for-in');
|
||||
var hasOwn = Object.prototype.hasOwnProperty;
|
||||
|
||||
module.exports = function forOwn(o, fn, thisArg) {
|
||||
forIn(o, function (val, key) {
|
||||
if (hasOwn.call(o, key)) {
|
||||
return fn.call(thisArg, o[key], key, o);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
},{"for-in":4}],4:[function(require,module,exports){
|
||||
/*!
|
||||
* for-in <https://github.com/jonschlinkert/for-in>
|
||||
*
|
||||
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function forIn(o, fn, thisArg) {
|
||||
for (var key in o) {
|
||||
if (fn.call(thisArg, o[key], key, o) === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
},{}],5:[function(require,module,exports){
|
||||
/*!
|
||||
* isobject <https://github.com/jonschlinkert/isobject>
|
||||
*
|
||||
* Copyright (c) 2014 Jon Schlinkert, contributors.
|
||||
* Licensed under the MIT License
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* is the value an object, and not an array?
|
||||
*
|
||||
* @param {*} `value`
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
module.exports = function isObject(o) {
|
||||
return o != null && typeof o === 'object'
|
||||
&& !Array.isArray(o);
|
||||
};
|
||||
},{}],6:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Concatenates two arrays, removing duplicates in the process and returns one array with unique values.
|
||||
* In case the elements in the array don't have a proper built in way to determine their identity,
|
||||
* a custom identity function must be provided.
|
||||
*
|
||||
* As an example, {Object}s all return '[ 'object' ]' when .toString()ed and therefore require a custom
|
||||
* identity function.
|
||||
*
|
||||
* @name exports
|
||||
* @function unique-concat
|
||||
* @param arr1 {Array} first batch of elements
|
||||
* @param arr2 {Array} second batch of elements
|
||||
* @param identity {Function} (optional) supply an alternative way to get an element's identity
|
||||
*/
|
||||
var go = module.exports = function uniqueConcat(arr1, arr2, identity) {
|
||||
|
||||
if (!arr1 || !arr2) throw new Error('Need two arrays to merge');
|
||||
if (!Array.isArray(arr1)) throw new Error('First argument is not an array, but a ' + typeof arr1);
|
||||
if (!Array.isArray(arr2)) throw new Error('Second argument is not an array, but a ' + typeof arr2);
|
||||
if (identity && typeof identity !== 'function') throw new Error('Third argument should be a function');
|
||||
|
||||
function hashify(acc, k) {
|
||||
acc[identity ? identity(k) : k] = k;
|
||||
return acc;
|
||||
}
|
||||
|
||||
var arr1Hash = arr1.reduce(hashify, {});
|
||||
var mergedHash = arr2.reduce(hashify, arr1Hash);
|
||||
|
||||
return Object.keys(mergedHash).map(function (key) { return mergedHash[key]; });
|
||||
};
|
||||
|
||||
},{}],7:[function(require,module,exports){
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
||||
|
||||
exports['default'] = createThenable;
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
|
||||
|
||||
function _defineProperty(obj, key, value) { return Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); }
|
||||
|
||||
var _uniqueConcat = require('unique-concat');
|
||||
|
||||
var _uniqueConcat2 = _interopRequireDefault(_uniqueConcat);
|
||||
|
||||
var _objectOmit = require('object-omit');
|
||||
|
||||
var _objectOmit2 = _interopRequireDefault(_objectOmit);
|
||||
|
||||
'use strict';
|
||||
|
||||
function createThenable(Promise, resolver) {
|
||||
return methods(Promise).reduce(createMethod, { then: then });
|
||||
function createMethod(thenable, name) {
|
||||
return _extends(thenable, _defineProperty({}, name, method(name)));
|
||||
}
|
||||
function method(name) {
|
||||
return function () {
|
||||
var _then;
|
||||
|
||||
return (_then = this.then())[name].apply(_then, arguments);
|
||||
};
|
||||
}
|
||||
function then() {
|
||||
var _ref;
|
||||
|
||||
return (_ref = new Promise(resolver)).then.apply(_ref, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
function methods(Promise) {
|
||||
return _uniqueConcat2['default'](['catch', 'finally'], Object.keys(_objectOmit2['default'](Promise.prototype, 'then')));
|
||||
}
|
||||
module.exports = exports['default'];
|
||||
/*onFulfill, onReject*/
|
||||
},{"object-omit":2,"unique-concat":6}],8:[function(require,module,exports){
|
||||
(function (global){
|
||||
/*! Native Promise Only
|
||||
v0.7.8-a (c) Kyle Simpson
|
||||
MIT License: http://getify.mit-license.org
|
||||
*/
|
||||
!function(t,n,e){n[t]=n[t]||e(),"undefined"!=typeof module&&module.exports?module.exports=n[t]:"function"==typeof define&&define.amd&&define(function(){return n[t]})}("Promise","undefined"!=typeof global?global:this,function(){"use strict";function t(t,n){l.add(t,n),h||(h=y(l.drain))}function n(t){var n,e=typeof t;return null==t||"object"!=e&&"function"!=e||(n=t.then),"function"==typeof n?n:!1}function e(){for(var t=0;t<this.chain.length;t++)o(this,1===this.state?this.chain[t].success:this.chain[t].failure,this.chain[t]);this.chain.length=0}function o(t,e,o){var r,i;try{e===!1?o.reject(t.msg):(r=e===!0?t.msg:e.call(void 0,t.msg),r===o.promise?o.reject(TypeError("Promise-chain cycle")):(i=n(r))?i.call(r,o.resolve,o.reject):o.resolve(r))}catch(c){o.reject(c)}}function r(o){var c,u,a=this;if(!a.triggered){a.triggered=!0,a.def&&(a=a.def);try{(c=n(o))?(u=new f(a),c.call(o,function(){r.apply(u,arguments)},function(){i.apply(u,arguments)})):(a.msg=o,a.state=1,a.chain.length>0&&t(e,a))}catch(s){i.call(u||new f(a),s)}}}function i(n){var o=this;o.triggered||(o.triggered=!0,o.def&&(o=o.def),o.msg=n,o.state=2,o.chain.length>0&&t(e,o))}function c(t,n,e,o){for(var r=0;r<n.length;r++)!function(r){t.resolve(n[r]).then(function(t){e(r,t)},o)}(r)}function f(t){this.def=t,this.triggered=!1}function u(t){this.promise=t,this.state=0,this.triggered=!1,this.chain=[],this.msg=void 0}function a(n){if("function"!=typeof n)throw TypeError("Not a function");if(0!==this.__NPO__)throw TypeError("Not a promise");this.__NPO__=1;var o=new u(this);this.then=function(n,r){var i={success:"function"==typeof n?n:!0,failure:"function"==typeof r?r:!1};return i.promise=new this.constructor(function(t,n){if("function"!=typeof t||"function"!=typeof n)throw TypeError("Not a function");i.resolve=t,i.reject=n}),o.chain.push(i),0!==o.state&&t(e,o),i.promise},this["catch"]=function(t){return this.then(void 0,t)};try{n.call(void 0,function(t){r.call(o,t)},function(t){i.call(o,t)})}catch(c){i.call(o,c)}}var s,h,l,p=Object.prototype.toString,y="undefined"!=typeof setImmediate?function(t){return setImmediate(t)}:setTimeout;try{Object.defineProperty({},"x",{}),s=function(t,n,e,o){return Object.defineProperty(t,n,{value:e,writable:!0,configurable:o!==!1})}}catch(d){s=function(t,n,e){return t[n]=e,t}}l=function(){function t(t,n){this.fn=t,this.self=n,this.next=void 0}var n,e,o;return{add:function(r,i){o=new t(r,i),e?e.next=o:n=o,e=o,o=void 0},drain:function(){var t=n;for(n=e=h=void 0;t;)t.fn.call(t.self),t=t.next}}}();var g=s({},"constructor",a,!1);return a.prototype=g,s(g,"__NPO__",0,!1),s(a,"resolve",function(t){var n=this;return t&&"object"==typeof t&&1===t.__NPO__?t:new n(function(n,e){if("function"!=typeof n||"function"!=typeof e)throw TypeError("Not a function");n(t)})}),s(a,"reject",function(t){return new this(function(n,e){if("function"!=typeof n||"function"!=typeof e)throw TypeError("Not a function");e(t)})}),s(a,"all",function(t){var n=this;return"[object Array]"!=p.call(t)?n.reject(TypeError("Not an array")):0===t.length?n.resolve([]):new n(function(e,o){if("function"!=typeof e||"function"!=typeof o)throw TypeError("Not a function");var r=t.length,i=Array(r),f=0;c(n,t,function(t,n){i[t]=n,++f===r&&e(i)},o)})}),s(a,"race",function(t){var n=this;return"[object Array]"!=p.call(t)?n.reject(TypeError("Not an array")):new n(function(e,o){if("function"!=typeof e||"function"!=typeof o)throw TypeError("Not a function");c(n,t,function(t,n){e(n)},o)})}),a});
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{}]},{},[1])(1)
|
||||
});
|
160
test/tests/preferences_syncTest.js
Normal file
160
test/tests/preferences_syncTest.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
describe("Sync Preferences", function () {
|
||||
var win, doc;
|
||||
before(function* () {
|
||||
// Load prefs with sync pane
|
||||
win = yield loadWindow("chrome://zotero/content/preferences/preferences.xul", {
|
||||
pane: 'zotero-prefpane-sync',
|
||||
tabIndex: 0
|
||||
});
|
||||
doc = win.document;
|
||||
let defer = Zotero.Promise.defer();
|
||||
let pane = doc.getElementById('zotero-prefpane-sync');
|
||||
if (!pane.loaded) {
|
||||
pane.addEventListener('paneload', function () {
|
||||
defer.resolve();
|
||||
});
|
||||
yield defer.promise;
|
||||
}
|
||||
});
|
||||
|
||||
after(function() {
|
||||
win.close();
|
||||
});
|
||||
|
||||
describe("Settings", function () {
|
||||
describe("Zotero Data Sync", function () {
|
||||
var getAPIKeyFromCredentialsStub, deleteAPIKey, indicatorElem;
|
||||
|
||||
var setCredentials = Zotero.Promise.coroutine(function* (username, password) {
|
||||
let usernameElem = doc.getElementById('sync-username-textbox');
|
||||
let passwordElem = doc.getElementById('sync-password');
|
||||
usernameElem.value = username;
|
||||
passwordElem.value = password;
|
||||
|
||||
// Triggered by `change` event for usernameElem and passwordElem;
|
||||
yield win.Zotero_Preferences.Sync.linkAccount();
|
||||
});
|
||||
|
||||
var apiKey = Zotero.Utilities.randomString(24);
|
||||
|
||||
var apiResponse = {
|
||||
key: apiKey,
|
||||
username: "Username",
|
||||
userID: 1,
|
||||
access: {}
|
||||
};
|
||||
|
||||
before(function* () {
|
||||
getAPIKeyFromCredentialsStub = sinon.stub(
|
||||
Zotero.Sync.APIClient.prototype, 'createAPIKeyFromCredentials');
|
||||
deleteAPIKey = sinon.stub(Zotero.Sync.APIClient.prototype, 'deleteAPIKey').resolves();
|
||||
indicatorElem = doc.getElementById('sync-status-indicator')
|
||||
sinon.stub(Zotero, 'alert');
|
||||
});
|
||||
|
||||
beforeEach(function* (){
|
||||
yield win.Zotero_Preferences.Sync.unlinkAccount(false);
|
||||
deleteAPIKey.reset();
|
||||
Zotero.alert.reset();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
Zotero.HTTP.mock = null;
|
||||
Zotero.alert.restore();
|
||||
getAPIKeyFromCredentialsStub.restore();
|
||||
deleteAPIKey.restore();
|
||||
win.close();
|
||||
});
|
||||
|
||||
it("should set API key and display full controls with correct credentials", function* () {
|
||||
getAPIKeyFromCredentialsStub.resolves(apiResponse);
|
||||
yield setCredentials("Username", "correctPassword");
|
||||
|
||||
assert.equal(Zotero.Sync.Data.Local.getAPIKey(), apiKey);
|
||||
assert.equal(doc.getElementById('sync-unauthorized').getAttribute('hidden'), 'true');
|
||||
});
|
||||
|
||||
|
||||
it("should display dialog when credentials incorrect", function* () {
|
||||
getAPIKeyFromCredentialsStub.resolves(false);
|
||||
yield setCredentials("Username", "incorrectPassword");
|
||||
|
||||
assert.isTrue(Zotero.alert.called);
|
||||
assert.equal(Zotero.Sync.Data.Local.getAPIKey(), "");
|
||||
assert.equal(doc.getElementById('sync-authorized').getAttribute('hidden'), 'true');
|
||||
});
|
||||
|
||||
|
||||
it("should delete API key and display auth form when 'Unlink Account' clicked", function* () {
|
||||
getAPIKeyFromCredentialsStub.resolves(apiResponse);
|
||||
yield setCredentials("Username", "correctPassword");
|
||||
assert.equal(Zotero.Sync.Data.Local.getAPIKey(), apiKey);
|
||||
|
||||
yield win.Zotero_Preferences.Sync.unlinkAccount(false);
|
||||
|
||||
assert.isTrue(deleteAPIKey.called);
|
||||
assert.equal(Zotero.Sync.Data.Local.getAPIKey(), "");
|
||||
assert.equal(doc.getElementById('sync-authorized').getAttribute('hidden'), 'true');
|
||||
});
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe("#checkUser()", function () {
|
||||
it("should prompt for user update and perform on accept", function* () {
|
||||
yield Zotero.Users.setCurrentUserID(1);
|
||||
yield Zotero.Users.setCurrentUsername("A");
|
||||
|
||||
waitForDialog(function (dialog) {
|
||||
var text = dialog.document.documentElement.textContent;
|
||||
var matches = text.match(/'[^']*'/g);
|
||||
assert.equal(matches.length, 4);
|
||||
assert.equal(matches[0], "'A'");
|
||||
assert.equal(matches[1], "'B'");
|
||||
assert.equal(matches[2], "'B'");
|
||||
assert.equal(matches[3], "'A'");
|
||||
});
|
||||
var cont = yield win.Zotero_Preferences.Sync.checkUser(2, "B");
|
||||
assert.isTrue(cont);
|
||||
|
||||
assert.equal(Zotero.Users.getCurrentUserID(), 2);
|
||||
assert.equal(Zotero.Users.getCurrentUsername(), "B");
|
||||
})
|
||||
|
||||
it("should prompt for user update and cancel", function* () {
|
||||
yield Zotero.Users.setCurrentUserID(1);
|
||||
yield Zotero.Users.setCurrentUsername("A");
|
||||
|
||||
waitForDialog(false, 'cancel');
|
||||
var cont = yield win.Zotero_Preferences.Sync.checkUser(2, "B");
|
||||
assert.isFalse(cont);
|
||||
|
||||
assert.equal(Zotero.Users.getCurrentUserID(), 1);
|
||||
assert.equal(Zotero.Users.getCurrentUsername(), "A");
|
||||
})
|
||||
|
||||
it("should update local relations when syncing for the first time", function* () {
|
||||
yield resetDB({
|
||||
thisArg: this,
|
||||
skipBundledFiles: true
|
||||
});
|
||||
|
||||
var item1 = yield createDataObject('item');
|
||||
var item2 = yield createDataObject(
|
||||
'item', { libraryID: Zotero.Libraries.publicationsLibraryID }
|
||||
);
|
||||
|
||||
yield item1.addLinkedItem(item2);
|
||||
|
||||
var cont = yield win.Zotero_Preferences.Sync.checkUser(1, "A");
|
||||
assert.isTrue(cont);
|
||||
|
||||
var json = yield item1.toJSON();
|
||||
var uri = json.relations[Zotero.Relations.linkedObjectPredicate][0];
|
||||
assert.notInclude(uri, 'users/local');
|
||||
assert.include(uri, 'users/1/publications');
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
|
@ -167,10 +167,8 @@ describe("Zotero.Sync.Runner", function () {
|
|||
|
||||
describe("#checkAccess()", function () {
|
||||
it("should check key access", function* () {
|
||||
spy = sinon.spy(runner, "checkUser");
|
||||
setResponse('keyInfo.fullAccess');
|
||||
var json = yield runner.checkAccess(runner.getAPIClient({ apiKey }));
|
||||
sinon.assert.calledWith(spy, 1, "Username");
|
||||
var compare = {};
|
||||
Object.assign(compare, responses.keyInfo.fullAccess.json);
|
||||
delete compare.key;
|
||||
|
@ -409,60 +407,7 @@ describe("Zotero.Sync.Runner", function () {
|
|||
assert.isTrue(Zotero.Groups.exists(groupData.json.id));
|
||||
})
|
||||
})
|
||||
|
||||
describe("#checkUser()", function () {
|
||||
it("should prompt for user update and perform on accept", function* () {
|
||||
waitForDialog(function (dialog) {
|
||||
var text = dialog.document.documentElement.textContent;
|
||||
var matches = text.match(/'[^']*'/g);
|
||||
assert.equal(matches.length, 4);
|
||||
assert.equal(matches[0], "'A'");
|
||||
assert.equal(matches[1], "'B'");
|
||||
assert.equal(matches[2], "'B'");
|
||||
assert.equal(matches[3], "'A'");
|
||||
});
|
||||
var cont = yield runner.checkUser(2, "B");
|
||||
assert.isTrue(cont);
|
||||
|
||||
assert.equal(Zotero.Users.getCurrentUserID(), 2);
|
||||
assert.equal(Zotero.Users.getCurrentUsername(), "B");
|
||||
})
|
||||
|
||||
it("should prompt for user update and cancel", function* () {
|
||||
yield Zotero.Users.setCurrentUserID(1);
|
||||
yield Zotero.Users.setCurrentUsername("A");
|
||||
|
||||
waitForDialog(false, 'cancel');
|
||||
var cont = yield runner.checkUser(2, "B");
|
||||
assert.isFalse(cont);
|
||||
|
||||
assert.equal(Zotero.Users.getCurrentUserID(), 1);
|
||||
assert.equal(Zotero.Users.getCurrentUsername(), "A");
|
||||
})
|
||||
|
||||
it("should update local relations when syncing for the first time", function* () {
|
||||
yield resetDB({
|
||||
thisArg: this,
|
||||
skipBundledFiles: true
|
||||
});
|
||||
|
||||
var item1 = yield createDataObject('item');
|
||||
var item2 = yield createDataObject(
|
||||
'item', { libraryID: Zotero.Libraries.publicationsLibraryID }
|
||||
);
|
||||
|
||||
yield item1.addLinkedItem(item2);
|
||||
|
||||
var cont = yield runner.checkUser(1, "A");
|
||||
assert.isTrue(cont);
|
||||
|
||||
var json = yield item1.toJSON();
|
||||
var uri = json.relations[Zotero.Relations.linkedObjectPredicate][0];
|
||||
assert.notInclude(uri, 'users/local');
|
||||
assert.include(uri, 'users/1/publications');
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe("#sync()", function () {
|
||||
before(function* () {
|
||||
yield resetDB({
|
||||
|
@ -718,4 +663,69 @@ describe("Zotero.Sync.Runner", function () {
|
|||
assert.isBelow(lastSyncTime, new Date().getTime());
|
||||
})
|
||||
})
|
||||
|
||||
describe("#createAPIKeyFromCredentials()", function() {
|
||||
var data = {
|
||||
name: "Automatic Zotero Client Key",
|
||||
username: "Username",
|
||||
access: {
|
||||
user: {
|
||||
library: true,
|
||||
files: true,
|
||||
notes: true,
|
||||
write: true
|
||||
},
|
||||
groups: {
|
||||
all: {
|
||||
library: true,
|
||||
write: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var correctPostData = Object.assign({password: 'correctPassword'}, data);
|
||||
var incorrectPostData = Object.assign({password: 'incorrectPassword'}, data);
|
||||
var responseData = Object.assign({userID: 1, key: apiKey}, data);
|
||||
|
||||
it("should return json with key when credentials valid", function* () {
|
||||
server.respond(function (req) {
|
||||
if (req.method == "POST") {
|
||||
var json = JSON.parse(req.requestBody);
|
||||
assert.deepEqual(json, correctPostData);
|
||||
req.respond(201, {}, JSON.stringify(responseData));
|
||||
}
|
||||
});
|
||||
|
||||
var json = yield runner.createAPIKeyFromCredentials('Username', 'correctPassword');
|
||||
assert.equal(json.key, apiKey);
|
||||
});
|
||||
|
||||
it("should return false when credentials invalid", function* () {
|
||||
server.respond(function (req) {
|
||||
if (req.method == "POST") {
|
||||
var json = JSON.parse(req.requestBody);
|
||||
assert.deepEqual(json, incorrectPostData);
|
||||
req.respond(403);
|
||||
}
|
||||
});
|
||||
|
||||
var key = yield runner.createAPIKeyFromCredentials('Username', 'incorrectPassword');
|
||||
assert.isFalse(key);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#deleteAPIKey()", function() {
|
||||
it("should send DELETE request with correct key", function* (){
|
||||
Zotero.Sync.Data.Local.setAPIKey(apiKey);
|
||||
|
||||
server.respond(function (req) {
|
||||
if (req.method == "DELETE") {
|
||||
assert.equal(req.url, baseURL + "keys/" + apiKey);
|
||||
}
|
||||
req.respond(204);
|
||||
});
|
||||
|
||||
yield runner.deleteAPIKey();
|
||||
});
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue