This commit is contained in:
Mo Bitar
2020-02-05 13:17:20 -06:00
parent 8c898b51be
commit 1ae3790ea3
48 changed files with 99518 additions and 2230 deletions

View File

@@ -3,6 +3,10 @@
import angular from 'angular'; import angular from 'angular';
import { configRoutes } from './routes'; import { configRoutes } from './routes';
import {
Application
} from './application';
import { import {
AppState AppState
} from './state'; } from './state';
@@ -54,14 +58,14 @@ import {
ArchiveManager, ArchiveManager,
AuthManager, AuthManager,
ComponentManager, ComponentManager,
DBManager, DatabaseManager,
DesktopManager, DesktopManager,
HttpManager, HttpManager,
KeyboardManager, KeyboardManager,
MigrationManager, MigrationManager,
ModelManager, ModelManager,
NativeExtManager, NativeExtManager,
PasscodeManager, LockManager,
PrivilegesManager, PrivilegesManager,
SessionHistory, SessionHistory,
SingletonManager, SingletonManager,
@@ -141,20 +145,21 @@ angular
// Services // Services
angular angular
.module('app') .module('app')
.service('application', Application)
.service('appState', AppState) .service('appState', AppState)
.service('preferencesManager', PreferencesManager) .service('preferencesManager', PreferencesManager)
.service('actionsManager', ActionsManager) .service('actionsManager', ActionsManager)
.service('archiveManager', ArchiveManager) .service('archiveManager', ArchiveManager)
.service('authManager', AuthManager) .service('authManager', AuthManager)
.service('componentManager', ComponentManager) .service('componentManager', ComponentManager)
.service('dbManager', DBManager) .service('databaseManager', DatabaseManager)
.service('desktopManager', DesktopManager) .service('desktopManager', DesktopManager)
.service('httpManager', HttpManager) .service('httpManager', HttpManager)
.service('keyboardManager', KeyboardManager) .service('keyboardManager', KeyboardManager)
.service('migrationManager', MigrationManager) .service('migrationManager', MigrationManager)
.service('modelManager', ModelManager) .service('modelManager', ModelManager)
.service('nativeExtManager', NativeExtManager) .service('nativeExtManager', NativeExtManager)
.service('passcodeManager', PasscodeManager) .service('lockManager', LockManager)
.service('privilegesManager', PrivilegesManager) .service('privilegesManager', PrivilegesManager)
.service('sessionHistory', SessionHistory) .service('sessionHistory', SessionHistory)
.service('singletonManager', SingletonManager) .service('singletonManager', SingletonManager)

View File

@@ -0,0 +1,55 @@
import {
SNApplication,
SNAlertManager
Platforms,
Environments
} from 'snjs';
import angular from 'angular';
import { AlertManager } from '@/services/alertManager'
import { WebDeviceInterface } from '@/web_device_interface';
export class Application extends SNApplication {
constructor(
desktopManager
) {
const deviceInterface = new WebDeviceInterface();
super({
platform: Platforms.Web,
namespace: '',
host: window._default_sync_server,
deviceInterface: deviceInterface,
swapClasses: [
{
swap: SNAlertManager,
with: AlertManager
}
]
});
this.desktopManager = desktopManager;
this.overrideComponentManagerFunctions();
}
overrideComponentManagerFunctions() {
function openModalComponent(component) {
var scope = this.$rootScope.$new(true);
scope.component = component;
var el = this.$compile("<component-modal component='component' class='sk-modal'></component-modal>")(scope);
angular.element(document.body).append(el);
}
function presentPermissionsDialog(dialog) {
const scope = this.$rootScope.$new(true);
scope.permissionsString = dialog.permissionsString;
scope.component = dialog.component;
scope.callback = dialog.callback;
var el = this.$compile("<permissions-modal component='component' permissions-string='permissionsString' callback='callback' class='sk-modal'></permissions-modal>")(scope);
angular.element(document.body).append(el);
}
this.componentManager.openModalComponent = openModalComponent;
this.componentManager.presentPermissionsDialog = presentPermissionsDialog;
this.componentManager.setDesktopManager(this.desktopManager);
}
}

View File

@@ -1,8 +1,14 @@
import angular from 'angular'; import angular from 'angular';
import { SFModelManager } from 'snjs'; import {
ApplicationEvents,
isPayloadSourceRetrieved,
CONTENT_TYPE_NOTE,
CONTENT_TYPE_TAG,
CONTENT_TYPE_COMPONENT,
ProtectedActions
} from 'snjs';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { KeyboardManager } from '@/services/keyboardManager'; import { KeyboardManager } from '@/services/keyboardManager';
import { PrivilegesManager } from '@/services/privilegesManager';
import template from '%/editor.pug'; import template from '%/editor.pug';
import { PureCtrl } from '@Controllers'; import { PureCtrl } from '@Controllers';
import { import {
@@ -53,32 +59,22 @@ class EditorCtrl extends PureCtrl {
constructor( constructor(
$timeout, $timeout,
$rootScope, $rootScope,
alertManager,
appState, appState,
authManager, application,
actionsManager, actionsManager,
componentManager,
desktopManager, desktopManager,
keyboardManager, keyboardManager,
modelManager,
preferencesManager, preferencesManager,
privilegesManager, sessionHistory /** Unused below, required to load globally */
sessionHistory /** Unused below, required to load globally */,
syncManager,
) { ) {
super($timeout); super($timeout);
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.alertManager = alertManager; this.application = application;
this.appState = appState; this.appState = appState;
this.actionsManager = actionsManager; this.actionsManager = actionsManager;
this.authManager = authManager;
this.componentManager = componentManager;
this.desktopManager = desktopManager; this.desktopManager = desktopManager;
this.keyboardManager = keyboardManager; this.keyboardManager = keyboardManager;
this.modelManager = modelManager;
this.preferencesManager = preferencesManager; this.preferencesManager = preferencesManager;
this.privilegesManager = privilegesManager;
this.syncManager = syncManager;
this.state = { this.state = {
componentStack: [], componentStack: [],
@@ -94,9 +90,9 @@ class EditorCtrl extends PureCtrl {
this.rightResizeControl = {}; this.rightResizeControl = {};
this.addAppStateObserver(); this.addAppStateObserver();
this.addSyncEventHandler(); this.addAppEventObserver();
this.addSyncStatusObserver(); this.addSyncStatusObserver();
this.addMappingObservers(); this.streamItems();
this.registerComponentHandler(); this.registerComponentHandler();
this.registerKeyboardShortcuts(); this.registerKeyboardShortcuts();
@@ -119,6 +115,111 @@ class EditorCtrl extends PureCtrl {
}); });
} }
streamItems() {
this.application.streamItems({
contentType: CONTENT_TYPE_NOTE,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
}
if (this.state.note.deleted || this.state.note.content.trashed) {
return;
}
if (!isPayloadSourceRetrieved(source)) {
return;
}
const matchingNote = items.find((item) => {
return item.uuid === this.state.note.uuid;
});
if (!matchingNote) {
return;
}
this.reloadTagsString();
}
});
this.application.streamItems({
contentType: CONTENT_TYPE_TAG,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
}
for (const tag of items) {
if (
!this.state.note.savedTagsString ||
tag.deleted ||
tag.hasRelationshipWithItem(this.state.note)
) {
this.reloadTagsString();
break;
}
}
}
});
this.application.streamItems({
contentType: CONTENT_TYPE_COMPONENT,
stream: async ({ items, source }) => {
if (!this.state.note) {
return;
}
/** Reload componentStack in case new ones were added or removed */
this.reloadComponentStackArray();
/** Observe editor changes to see if the current note should update its editor */
const editors = items.filter(function (item) {
return item.isEditor();
});
if (editors.length === 0) {
return;
}
/** Find the most recent editor for note */
const editor = this.editorForNote(this.state.note);
this.setState({
selectedEditor: editor
});
if (!editor) {
this.reloadFont();
}
}
});
}
addAppEventObserver() {
this.application.addEventObserver((eventName) => {
if (!this.state.note) {
return;
}
if (eventName === ApplicationEvents.HighLatencySync) {
this.setState({
syncTakingTooLong: true
});
} else if (eventName === ApplicationEvents.CompletedSync) {
this.setState({
syncTakingTooLong: false
});
if (this.state.note.dirty) {
/** if we're still dirty, don't change status, a sync is likely upcoming. */
} else {
const saved = this.state.note.updated_at > this.state.note.lastSyncBegan;
const isInErrorState = this.state.saveError;
if (isInErrorState || saved) {
this.showAllChangesSavedStatus();
}
}
} else if (eventName === ApplicationEvents.FailedSync) {
/**
* Only show error status in editor if the note is dirty.
* Otherwise, it means the originating sync came from somewhere else
* and we don't want to display an error here.
*/
if (this.state.note.dirty) {
this.showErrorStatus();
}
}
});
}
async handleNoteSelectionChange(note, previousNote) { async handleNoteSelectionChange(note, previousNote) {
this.setState({ this.setState({
note: this.appState.getSelectedNote(), note: this.appState.getSelectedNote(),
@@ -164,124 +265,19 @@ class EditorCtrl extends PureCtrl {
this.reloadComponentContext(); this.reloadComponentContext();
} }
addMappingObservers() {
this.modelManager.addItemSyncObserver(
'editor-note-observer',
'Note',
(allItems, validItems, deletedItems, source) => {
if (!this.state.note) {
return;
}
if (this.state.note.deleted || this.state.note.content.trashed) {
return;
}
if (!SFModelManager.isMappingSourceRetrieved(source)) {
return;
}
const matchingNote = allItems.find((item) => {
return item.uuid === this.state.note.uuid;
});
if (!matchingNote) {
return;
}
this.reloadTagsString();
});
this.modelManager.addItemSyncObserver(
'editor-tag-observer',
'Tag',
(allItems, validItems, deletedItems, source) => {
if (!this.state.note) {
return;
}
for (const tag of allItems) {
if (
!this.state.note.savedTagsString ||
tag.deleted ||
tag.hasRelationshipWithItem(this.state.note)
) {
this.reloadTagsString();
break;
}
}
});
this.modelManager.addItemSyncObserver(
'editor-component-observer',
'SN|Component',
(allItems, validItems, deletedItems, source) => {
if (!this.state.note) {
return;
}
/** Reload componentStack in case new ones were added or removed */
this.reloadComponentStackArray();
/** Observe editor changes to see if the current note should update its editor */
const editors = allItems.filter(function (item) {
return item.isEditor();
});
if (editors.length === 0) {
return;
}
/** Find the most recent editor for note */
const editor = this.editorForNote(this.state.note);
this.setState({
selectedEditor: editor
});
if (!editor) {
this.reloadFont();
}
});
}
addSyncEventHandler() {
this.syncManager.addEventHandler((eventName, data) => {
if (!this.state.note) {
return;
}
if (eventName === 'sync:taking-too-long') {
this.setState({
syncTakingTooLong: true
});
} else if (eventName === 'sync:completed') {
this.setState({
syncTakingTooLong: false
});
if (this.state.note.dirty) {
/** if we're still dirty, don't change status, a sync is likely upcoming. */
} else {
const savedItem = data.savedItems.find((item) => {
return item.uuid === this.state.note.uuid;
});
const isInErrorState = this.state.saveError;
if (isInErrorState || savedItem) {
this.showAllChangesSavedStatus();
}
}
} else if (eventName === 'sync:error') {
/**
* Only show error status in editor if the note is dirty.
* Otherwise, it means the originating sync came from somewhere else
* and we don't want to display an error here.
*/
if (this.state.note.dirty) {
this.showErrorStatus();
}
}
});
}
addSyncStatusObserver() { addSyncStatusObserver() {
this.syncStatusObserver = this.syncManager. /** @todo */
registerSyncStatusObserver((status) => { // this.syncStatusObserver = this.syncManager.
if (status.localError) { // registerSyncStatusObserver((status) => {
this.$timeout(() => { // if (status.localError) {
this.showErrorStatus({ // this.$timeout(() => {
message: "Offline Saving Issue", // this.showErrorStatus({
desc: "Changes not saved" // message: "Offline Saving Issue",
}); // desc: "Changes not saved"
}, 500); // });
} // }, 500);
}); // }
// });
} }
editorForNote(note) { editorForNote(note) {
@@ -332,7 +328,7 @@ class EditorCtrl extends PureCtrl {
APP_DATA_KEY_PREFERS_PLAIN_EDITOR, APP_DATA_KEY_PREFERS_PLAIN_EDITOR,
false false
); );
this.modelManager.setItemDirty(this.state.note); this.application.setItemNeedsSync({ item: this.state.note });
} }
this.associateComponentWithCurrentNote(editor); this.associateComponentWithCurrentNote(editor);
} else { } else {
@@ -342,7 +338,7 @@ class EditorCtrl extends PureCtrl {
APP_DATA_KEY_PREFERS_PLAIN_EDITOR, APP_DATA_KEY_PREFERS_PLAIN_EDITOR,
true true
); );
this.modelManager.setItemDirty(this.state.note); this.application.setItemNeedsSync({ item: this.state.note });
} }
this.reloadFont(); this.reloadFont();
@@ -356,7 +352,7 @@ class EditorCtrl extends PureCtrl {
} }
/** Dirtying can happen above */ /** Dirtying can happen above */
this.syncManager.sync(); this.application.sync();
} }
hasAvailableExtensions() { hasAvailableExtensions() {
@@ -383,13 +379,13 @@ class EditorCtrl extends PureCtrl {
const note = this.state.note; const note = this.state.note;
note.dummy = false; note.dummy = false;
if (note.deleted) { if (note.deleted) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_DELETED_NOTE text: STRING_DELETED_NOTE
}); });
return; return;
} }
if (!this.modelManager.findItem(note.uuid)) { if (!this.application.findItem({ uuid: note.uuid })) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_INVALID_NOTE text: STRING_INVALID_NOTE
}); });
return; return;
@@ -405,28 +401,20 @@ class EditorCtrl extends PureCtrl {
note.content.preview_plain = previewPlain; note.content.preview_plain = previewPlain;
note.content.preview_html = null; note.content.preview_html = null;
} }
this.modelManager.setItemDirty( this.application.setItemNeedsSync({
note, item: note,
true, updateUserModifiedDate: updateClientModified
updateClientModified });
);
if (this.saveTimeout) { if (this.saveTimeout) {
this.$timeout.cancel(this.saveTimeout); this.$timeout.cancel(this.saveTimeout);
} }
const noDebounce = bypassDebouncer || this.authManager.offline(); const noDebounce = bypassDebouncer || this.application.noAccount();
const syncDebouceMs = noDebounce const syncDebouceMs = noDebounce
? SAVE_TIMEOUT_NO_DEBOUNCE ? SAVE_TIMEOUT_NO_DEBOUNCE
: SAVE_TIMEOUT_DEBOUNCE; : SAVE_TIMEOUT_DEBOUNCE;
this.saveTimeout = this.$timeout(() => { this.saveTimeout = this.$timeout(() => {
this.syncManager.sync().then((response) => { this.application.sync();
if (response && response.error && !this.didShowErrorAlert) {
this.didShowErrorAlert = true;
this.alertManager.alert({
text: STRING_GENERIC_SAVE_ERROR
});
}
});
}, syncDebouceMs); }, syncDebouceMs);
} }
@@ -443,7 +431,7 @@ class EditorCtrl extends PureCtrl {
syncTakingTooLong: false syncTakingTooLong: false
}); });
let status = "All changes saved"; let status = "All changes saved";
if (this.authManager.offline()) { if (this.application.noAccount()) {
status += " (offline)"; status += " (offline)";
} }
this.setStatus( this.setStatus(
@@ -542,14 +530,14 @@ class EditorCtrl extends PureCtrl {
async deleteNote(permanently) { async deleteNote(permanently) {
if (this.state.note.dummy) { if (this.state.note.dummy) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_DELETE_PLACEHOLDER_ATTEMPT text: STRING_DELETE_PLACEHOLDER_ATTEMPT
}); });
return; return;
} }
const run = () => { const run = () => {
if (this.state.note.locked) { if (this.state.note.locked) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_DELETE_LOCKED_ATTEMPT text: STRING_DELETE_LOCKED_ATTEMPT
}); });
return; return;
@@ -561,7 +549,7 @@ class EditorCtrl extends PureCtrl {
title: title, title: title,
permanently: permanently permanently: permanently
}); });
this.alertManager.confirm({ this.application.alertManager.confirm({
text: text, text: text,
destructive: true, destructive: true,
onConfirm: () => { onConfirm: () => {
@@ -579,12 +567,12 @@ class EditorCtrl extends PureCtrl {
} }
}); });
}; };
const requiresPrivilege = await this.privilegesManager.actionRequiresPrivilege( const requiresPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionDeleteNote ProtectedActions.DeleteNote
); );
if (requiresPrivilege) { if (requiresPrivilege) {
this.privilegesManager.presentPrivilegesModal( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionDeleteNote, ProtectedActions.DeleteNote,
() => { () => {
run(); run();
} }
@@ -595,17 +583,17 @@ class EditorCtrl extends PureCtrl {
} }
performNoteDeletion(note) { performNoteDeletion(note) {
this.modelManager.setItemToBeDeleted(note); this.application.deleteItem({ item: note });
if (note === this.state.note) { if (note === this.state.note) {
this.setState({ this.setState({
note: null note: null
}); });
} }
if (note.dummy) { if (note.dummy) {
this.modelManager.removeItemLocally(note); this.application.deleteItemLocally({ item: note });
return; return;
} }
this.syncManager.sync(); this.application.sync();
} }
restoreTrashedNote() { restoreTrashedNote() {
@@ -622,17 +610,17 @@ class EditorCtrl extends PureCtrl {
} }
getTrashCount() { getTrashCount() {
return this.modelManager.trashedItems().length; return this.application.getTrashedItems().length;
} }
emptyTrash() { emptyTrash() {
const count = this.getTrashCount(); const count = this.getTrashCount();
this.alertManager.confirm({ this.application.alertManager.confirm({
text: StringEmptyTrash({ count }), text: StringEmptyTrash({ count }),
destructive: true, destructive: true,
onConfirm: () => { onConfirm: () => {
this.modelManager.emptyTrash(); this.application.emptyTrash();
this.syncManager.sync(); this.application.sync();
} }
}); });
} }
@@ -666,12 +654,12 @@ class EditorCtrl extends PureCtrl {
dontUpdatePreviews: true dontUpdatePreviews: true
}); });
/** Show privilegesManager if protection is not yet set up */ /** Show privileges manager if protection is not yet set up */
this.privilegesManager.actionHasPrivilegesConfigured( this.application.privilegesManager.actionHasPrivilegesConfigured(
PrivilegesManager.ActionViewProtectedNotes ProtectedActions.ViewProtectedNotes
).then((configured) => { ).then((configured) => {
if (!configured) { if (!configured) {
this.privilegesManager.presentPrivilegesManagementModal(); this.godService.presentPrivilegesManagementModal();
} }
}); });
} }
@@ -709,7 +697,7 @@ class EditorCtrl extends PureCtrl {
return currentTag.title; return currentTag.title;
}); });
strings.push(tag.title); strings.push(tag.title);
this.saveTags({ strings: strings}); this.saveTags({ strings: strings });
} }
removeTag(tag) { removeTag(tag) {
@@ -721,7 +709,7 @@ class EditorCtrl extends PureCtrl {
this.saveTags({ strings: strings }); this.saveTags({ strings: strings });
} }
saveTags({strings} = {}) { saveTags({ strings } = {}) {
if (!strings && this.state.mutable.tagsString === this.state.note.tagsString()) { if (!strings && this.state.mutable.tagsString === this.state.note.tagsString()) {
return; return;
} }
@@ -743,7 +731,7 @@ class EditorCtrl extends PureCtrl {
for (const tagToRemove of toRemove) { for (const tagToRemove of toRemove) {
tagToRemove.removeItemAsRelationship(this.state.note); tagToRemove.removeItemAsRelationship(this.state.note);
} }
this.modelManager.setItemsDirty(toRemove); this.application.setItemsNeedsSync({ items: toRemove });
const tags = []; const tags = [];
for (const tagString of strings) { for (const tagString of strings) {
const existingRelationship = _.find( const existingRelationship = _.find(
@@ -752,15 +740,14 @@ class EditorCtrl extends PureCtrl {
); );
if (!existingRelationship) { if (!existingRelationship) {
tags.push( tags.push(
this.modelManager.findOrCreateTagByTitle(tagString) this.application.findOrCreateTag({ title: tagString })
); );
} }
} }
for (const tag of tags) { for (const tag of tags) {
tag.addItemAsRelationship(this.state.note); tag.addItemAsRelationship(this.state.note);
} }
this.modelManager.setItemsDirty(tags); this.application.saveItems({ items: tags });
this.syncManager.sync();
this.reloadTagsString(); this.reloadTagsString();
} }
@@ -977,12 +964,12 @@ class EditorCtrl extends PureCtrl {
} }
else if (action === 'associate-item') { else if (action === 'associate-item') {
if (data.item.content_type === 'Tag') { if (data.item.content_type === 'Tag') {
const tag = this.modelManager.findItem(data.item.uuid); const tag = this.application.findItem({ uuid: data.item.uuid });
this.addTag(tag); this.addTag(tag);
} }
} }
else if (action === 'deassociate-item') { else if (action === 'deassociate-item') {
const tag = this.modelManager.findItem(data.item.uuid); const tag = this.application.findItem({ uuid: data.item.uuid });
this.removeTag(tag); this.removeTag(tag);
} }
else if (action === 'save-items') { else if (action === 'save-items') {
@@ -1049,8 +1036,7 @@ class EditorCtrl extends PureCtrl {
component.disassociatedItemIds.push(this.state.note.uuid); component.disassociatedItemIds.push(this.state.note.uuid);
} }
this.modelManager.setItemDirty(component); this.application.saveItem({ item: component });
this.syncManager.sync();
} }
associateComponentWithCurrentNote(component) { associateComponentWithCurrentNote(component) {
@@ -1063,8 +1049,7 @@ class EditorCtrl extends PureCtrl {
component.associatedItemIds.push(this.state.note.uuid); component.associatedItemIds.push(this.state.note.uuid);
} }
this.modelManager.setItemDirty(component); this.application.saveItem({ item: component });
this.syncManager.sync();
} }
registerKeyboardShortcuts() { registerKeyboardShortcuts() {

View File

@@ -1,5 +1,9 @@
import { PrivilegesManager } from '@/services/privilegesManager';
import { dateToLocalizedString } from '@/utils'; import { dateToLocalizedString } from '@/utils';
import {
ApplicationEvents,
TIMING_STRATEGY_FORCE_SPAWN_NEW,
ProtectedActions
} from 'snjs';
import template from '%/footer.pug'; import template from '%/footer.pug';
import { import {
APP_STATE_EVENT_EDITOR_FOCUSED, APP_STATE_EVENT_EDITOR_FOCUSED,
@@ -18,29 +22,19 @@ class FooterCtrl {
constructor( constructor(
$rootScope, $rootScope,
$timeout, $timeout,
alertManager,
appState, appState,
authManager, application,
componentManager,
modelManager,
nativeExtManager, nativeExtManager,
passcodeManager,
privilegesManager,
statusManager, statusManager,
syncManager, godService
) { ) {
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.$timeout = $timeout; this.$timeout = $timeout;
this.alertManager = alertManager; this.application = application;
this.appState = appState; this.appState = appState;
this.authManager = authManager;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.nativeExtManager = nativeExtManager; this.nativeExtManager = nativeExtManager;
this.passcodeManager = passcodeManager;
this.privilegesManager = privilegesManager;
this.statusManager = statusManager; this.statusManager = statusManager;
this.syncManager = syncManager; this.godService = godService;
this.rooms = []; this.rooms = [];
this.themesWithIcons = []; this.themesWithIcons = [];
@@ -48,13 +42,13 @@ class FooterCtrl {
this.addAppStateObserver(); this.addAppStateObserver();
this.updateOfflineStatus(); this.updateOfflineStatus();
this.addSyncEventHandler(); this.addAppEventObserver();
this.findErrors(); this.findErrors();
this.registerMappingObservers(); this.streamItems();
this.registerComponentHandler(); this.registerComponentHandler();
this.addRootScopeListeners(); this.addRootScopeListeners();
this.authManager.checkForSecurityUpdate().then((available) => { this.godService.checkForSecurityUpdate().then((available) => {
this.securityUpdateAvailable = available; this.securityUpdateAvailable = available;
}); });
this.statusManager.addStatusObserver((string) => { this.statusManager.addStatusObserver((string) => {
@@ -66,7 +60,7 @@ class FooterCtrl {
addRootScopeListeners() { addRootScopeListeners() {
this.$rootScope.$on("security-update-status-changed", () => { this.$rootScope.$on("security-update-status-changed", () => {
this.securityUpdateAvailable = this.authManager.securityUpdateAvailable; this.securityUpdateAvailable = this.godService.securityUpdateAvailable;
}); });
this.$rootScope.$on("reload-ext-data", () => { this.$rootScope.$on("reload-ext-data", () => {
this.reloadExtendedData(); this.reloadExtendedData();
@@ -108,35 +102,34 @@ class FooterCtrl {
}); });
} }
addSyncEventHandler() { addAppEventObserver() {
this.syncManager.addEventHandler((syncEvent, data) => { this.application.addEventHandler((eventName) => {
this.$timeout(() => { if (eventName === ApplicationEvents.LoadedLocalData) {
if(syncEvent === "local-data-loaded") { if(this.offline && this.application.getNoteCount() === 0) {
if(this.offline && this.modelManager.noteCount() === 0) { this.showAccountMenu = true;
this.showAccountMenu = true;
}
} else if(syncEvent === "enter-out-of-sync") {
this.outOfSync = true;
} else if(syncEvent === "exit-out-of-sync") {
this.outOfSync = false;
} else if(syncEvent === 'sync:completed') {
this.syncUpdated();
this.findErrors();
this.updateOfflineStatus();
} else if(syncEvent === 'sync:error') {
this.findErrors();
this.updateOfflineStatus();
} }
}); } else if (eventName === ApplicationEvents.EnteredOutOfSync) {
this.outOfSync = true;
} else if (eventName === ApplicationEvents.ExitedOutOfSync) {
this.outOfSync = false;
} else if (eventName === ApplicationEvents.CompletedSync) {
this.syncUpdated();
this.findErrors();
this.updateOfflineStatus();
} else if (eventName === ApplicationEvents.FailedSync) {
this.findErrors();
this.updateOfflineStatus();
}
}); });
} }
registerMappingObservers() { streamItems() {
this.modelManager.addItemSyncObserver( this.application.streamItems({
'room-bar', contentType: CONTENT_TYPE_COMPONENT,
'SN|Component', stream: async () => {
(allItems, validItems, deletedItems, source) => { this.rooms = this.application.getItems({
this.rooms = this.modelManager.components.filter((candidate) => { contentType: CONTENT_TYPE_COMPONENT
}).filter((candidate) => {
return candidate.area === 'rooms' && !candidate.deleted; return candidate.area === 'rooms' && !candidate.deleted;
}); });
if(this.queueExtReload) { if(this.queueExtReload) {
@@ -144,14 +137,14 @@ class FooterCtrl {
this.reloadExtendedData(); this.reloadExtendedData();
} }
} }
); });
this.modelManager.addItemSyncObserver( this.application.streamItems({
'footer-bar-themes', contentType: 'SN|Theme',
'SN|Theme', stream: async () => {
(allItems, validItems, deletedItems, source) => { const themes = this.application.getDisplayableItems({
const themes = this.modelManager.validItemsForContentType('SN|Theme') contentType: CONTENT_TYPE_THEME
.filter((candidate) => { }).filter((candidate) => {
return ( return (
!candidate.deleted && !candidate.deleted &&
candidate.content.package_info && candidate.content.package_info &&
@@ -170,7 +163,7 @@ class FooterCtrl {
} }
registerComponentHandler() { registerComponentHandler() {
this.componentManager.registerHandler({ this.application.componentManager.registerHandler({
identifier: "roomBar", identifier: "roomBar",
areas: ["rooms", "modal"], areas: ["rooms", "modal"],
activationHandler: (component) => {}, activationHandler: (component) => {},
@@ -215,19 +208,19 @@ class FooterCtrl {
} }
getUser() { getUser() {
return this.authManager.user; return this.application.getUser();
} }
updateOfflineStatus() { updateOfflineStatus() {
this.offline = this.authManager.offline(); this.offline = this.application.noUser();
} }
openSecurityUpdate() { openSecurityUpdate() {
this.authManager.presentPasswordWizard('upgrade-security'); this.godService.presentPasswordWizard('upgrade-security');
} }
findErrors() { findErrors() {
this.error = this.syncManager.syncStatus.error; this.error = this.application.getSyncStatus().error;
} }
accountMenuPressed() { accountMenuPressed() {
@@ -244,7 +237,7 @@ class FooterCtrl {
} }
hasPasscode() { hasPasscode() {
return this.passcodeManager.hasPasscode(); return this.application.hasPasscode();
} }
lockApp() { lockApp() {
@@ -253,15 +246,15 @@ class FooterCtrl {
refreshData() { refreshData() {
this.isRefreshing = true; this.isRefreshing = true;
this.syncManager.sync({ this.application.sync({
force: true, timingStrategy: TIMING_STRATEGY_FORCE_SPAWN_NEW,
performIntegrityCheck: true checkIntegrity: true
}).then((response) => { }).then((response) => {
this.$timeout(() => { this.$timeout(() => {
this.isRefreshing = false; this.isRefreshing = false;
}, 200); }, 200);
if(response && response.error) { if(response && response.error) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_GENERIC_SYNC_ERROR text: STRING_GENERIC_SYNC_ERROR
}); });
} else { } else {
@@ -280,7 +273,7 @@ class FooterCtrl {
clickedNewUpdateAnnouncement() { clickedNewUpdateAnnouncement() {
this.newUpdateAvailable = false; this.newUpdateAvailable = false;
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_NEW_UPDATE_READY text: STRING_NEW_UPDATE_READY
}); });
} }
@@ -324,7 +317,7 @@ class FooterCtrl {
} }
selectShortcut(shortcut) { selectShortcut(shortcut) {
this.componentManager.toggleComponent(shortcut.component); this.application.componentManager.toggleComponent(shortcut.component);
} }
onRoomDismiss(room) { onRoomDismiss(room) {
@@ -345,12 +338,12 @@ class FooterCtrl {
}; };
if(!room.showRoom) { if(!room.showRoom) {
const requiresPrivilege = await this.privilegesManager.actionRequiresPrivilege( const requiresPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManageExtensions ProtectedActions.ManageExtensions
); );
if(requiresPrivilege) { if(requiresPrivilege) {
this.privilegesManager.presentPrivilegesModal( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionManageExtensions, ProtectedActions.ManageExtensions,
run run
); );
} else { } else {
@@ -362,7 +355,7 @@ class FooterCtrl {
} }
clickOutsideAccountMenu() { clickOutsideAccountMenu() {
if(this.privilegesManager.authenticationInProgress()) { if(this.application.privilegesManager.authenticationInProgress()) {
return; return;
} }
this.showAccountMenu = false; this.showAccountMenu = false;

View File

@@ -1,4 +1,7 @@
import template from '%/lock-screen.pug'; import template from '%/lock-screen.pug';
import {
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '@/state';
const ELEMENT_ID_PASSCODE_INPUT = 'passcode-input'; const ELEMENT_ID_PASSCODE_INPUT = 'passcode-input';
@@ -8,15 +11,14 @@ class LockScreenCtrl {
constructor( constructor(
$scope, $scope,
alertManager, alertManager,
authManager, application,
passcodeManager, appState
) { ) {
this.$scope = $scope; this.$scope = $scope;
this.alertManager = alertManager; this.alertManager = alertManager;
this.authManager = authManager; this.application = application;
this.passcodeManager = passcodeManager; this.appState = appState;
this.formData = {}; this.formData = {};
this.addVisibilityObserver(); this.addVisibilityObserver();
this.addDestroyHandler(); this.addDestroyHandler();
} }
@@ -29,16 +31,13 @@ class LockScreenCtrl {
addDestroyHandler() { addDestroyHandler() {
this.$scope.$on('$destroy', () => { this.$scope.$on('$destroy', () => {
this.passcodeManager.removeVisibilityObserver( this.unregisterObserver();
this.visibilityObserver
);
}); });
} }
addVisibilityObserver() { addVisibilityObserver() {
this.visibilityObserver = this.passcodeManager this.unregisterObserver = this.appState.addObserver((eventName, data) => {
.addVisibilityObserver((visible) => { if (eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
if(visible) {
const input = this.passcodeInput; const input = this.passcodeInput;
if(input) { if(input) {
input.focus(); input.focus();
@@ -47,7 +46,7 @@ class LockScreenCtrl {
}); });
} }
submitPasscodeForm($event) { async submitPasscodeForm($event) {
if( if(
!this.formData.passcode || !this.formData.passcode ||
this.formData.passcode.length === 0 this.formData.passcode.length === 0
@@ -55,21 +54,15 @@ class LockScreenCtrl {
return; return;
} }
this.passcodeInput.blur(); this.passcodeInput.blur();
this.passcodeManager.unlock( const success = await this.onValue()(this.formData.passcode);
this.formData.passcode, if(!success) {
(success) => { this.alertManager.alert({
if(!success) { text: "Invalid passcode. Please try again.",
this.alertManager.alert({ onClose: () => {
text: "Invalid passcode. Please try again.", this.passcodeInput.focus();
onClose: () => {
this.passcodeInput.focus();
}
});
} else {
this.onSuccess()();
} }
} });
); }
} }
forgotPasscode() { forgotPasscode() {
@@ -80,10 +73,9 @@ class LockScreenCtrl {
this.alertManager.confirm({ this.alertManager.confirm({
text: "Are you sure you want to clear all local data?", text: "Are you sure you want to clear all local data?",
destructive: true, destructive: true,
onConfirm: () => { onConfirm: async () => {
this.authManager.signout(true).then(() => { await this.application.signOut();
window.location.reload(); await this.application.restart();
});
} }
}); });
} }
@@ -97,7 +89,7 @@ export class LockScreen {
this.controllerAs = 'ctrl'; this.controllerAs = 'ctrl';
this.bindToController = true; this.bindToController = true;
this.scope = { this.scope = {
onSuccess: '&', onValue: '&',
}; };
} }
} }

View File

@@ -1,7 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import angular from 'angular'; import angular from 'angular';
import template from '%/notes.pug'; import template from '%/notes.pug';
import { SFAuthManager } from 'snjs'; import { ApplicationEvents, CONTENT_TYPE_NOTE, CONTENT_TYPE_TAG } from 'snjs';
import { KeyboardManager } from '@/services/keyboardManager'; import { KeyboardManager } from '@/services/keyboardManager';
import { PureCtrl } from '@Controllers'; import { PureCtrl } from '@Controllers';
import { import {
@@ -48,25 +48,19 @@ class NotesCtrl extends PureCtrl {
constructor( constructor(
$timeout, $timeout,
$rootScope, $rootScope,
application,
appState, appState,
authManager,
desktopManager, desktopManager,
keyboardManager, keyboardManager,
modelManager,
preferencesManager, preferencesManager,
privilegesManager,
syncManager,
) { ) {
super($timeout); super($timeout);
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.application = application;
this.appState = appState; this.appState = appState;
this.authManager = authManager;
this.desktopManager = desktopManager; this.desktopManager = desktopManager;
this.keyboardManager = keyboardManager; this.keyboardManager = keyboardManager;
this.modelManager = modelManager;
this.preferencesManager = preferencesManager; this.preferencesManager = preferencesManager;
this.privilegesManager = privilegesManager;
this.syncManager = syncManager;
this.state = { this.state = {
notes: [], notes: [],
@@ -90,9 +84,8 @@ class NotesCtrl extends PureCtrl {
}; };
this.addAppStateObserver(); this.addAppStateObserver();
this.addSignInObserver(); this.addAppEventObserver();
this.addSyncEventHandler(); this.streamNotesAndTags();
this.addMappingObserver();
this.reloadPreferences(); this.reloadPreferences();
this.resetPagination(); this.resetPagination();
this.registerKeyboardShortcuts(); this.registerKeyboardShortcuts();
@@ -116,12 +109,12 @@ class NotesCtrl extends PureCtrl {
}); });
} }
addSignInObserver() { addAppEventObserver() {
this.authManager.addEventHandler((event) => { this.application.addEventObserver((eventName) => {
if (event === SFAuthManager.DidSignInEvent) { if (eventName === ApplicationEvents.SignedIn) {
/** Delete dummy note if applicable */ /** Delete dummy note if applicable */
if (this.state.selectedNote && this.state.selectedNote.dummy) { if (this.state.selectedNote && this.state.selectedNote.dummy) {
this.modelManager.removeItemLocally(this.state.selectedNote); this.application.removeItemLocally({ item: this.state.selectedNote });
this.selectNote(null).then(() => { this.selectNote(null).then(() => {
this.reloadNotes(); this.reloadNotes();
}); });
@@ -132,17 +125,11 @@ class NotesCtrl extends PureCtrl {
*/ */
this.createDummyOnSynCompletionIfNoNotes = true; this.createDummyOnSynCompletionIfNoNotes = true;
} }
} } else if (eventName === ApplicationEvents.LoadedLocalData) {
});
}
addSyncEventHandler() {
this.syncManager.addEventHandler((syncEvent, data) => {
if (syncEvent === 'local-data-loaded') {
if (this.state.notes.length === 0) { if (this.state.notes.length === 0) {
this.createNewNote(); this.createNewNote();
} }
} else if (syncEvent === 'sync:completed') { } else if(eventName === ApplicationEvents.CompletedSync) {
if (this.createDummyOnSynCompletionIfNoNotes && this.state.notes.length === 0) { if (this.createDummyOnSynCompletionIfNoNotes && this.state.notes.length === 0) {
this.createDummyOnSynCompletionIfNoNotes = false; this.createDummyOnSynCompletionIfNoNotes = false;
this.createNewNote(); this.createNewNote();
@@ -151,11 +138,10 @@ class NotesCtrl extends PureCtrl {
}); });
} }
addMappingObserver() { streamNotesAndTags() {
this.modelManager.addItemSyncObserver( this.application.streamItems({
'note-list', contentType: [CONTENT_TYPE_NOTE, CONTENT_TYPE_TAG],
'*', stream: async ({ items }) => {
async (allItems, validItems, deletedItems, source, sourceKey) => {
await this.reloadNotes(); await this.reloadNotes();
const selectedNote = this.state.selectedNote; const selectedNote = this.state.selectedNote;
if (selectedNote) { if (selectedNote) {
@@ -169,18 +155,19 @@ class NotesCtrl extends PureCtrl {
} }
/** Note has changed values, reset its flags */ /** Note has changed values, reset its flags */
const notes = allItems.filter((item) => item.content_type === 'Note'); const notes = items.filter((item) => item.content_type === CONTENT_TYPE_NOTE);
for (const note of notes) { for (const note of notes) {
this.loadFlagsForNote(note); this.loadFlagsForNote(note);
note.cachedCreatedAtString = note.createdAtString(); note.cachedCreatedAtString = note.createdAtString();
note.cachedUpdatedAtString = note.updatedAtString(); note.cachedUpdatedAtString = note.updatedAtString();
} }
}); }
});
} }
async handleTagChange(tag, previousTag) { async handleTagChange(tag, previousTag) {
if (this.state.selectedNote && this.state.selectedNote.dummy) { if (this.state.selectedNote && this.state.selectedNote.dummy) {
this.modelManager.removeItemLocally(this.state.selectedNote); this.application.removeItemLocally({item: this.state.selectedNote});
if (previousTag) { if (previousTag) {
_.remove(previousTag.notes, this.state.selectedNote); _.remove(previousTag.notes, this.state.selectedNote);
} }
@@ -201,7 +188,7 @@ class NotesCtrl extends PureCtrl {
if (this.state.notes.length > 0) { if (this.state.notes.length > 0) {
this.selectFirstNote(); this.selectFirstNote();
} else if (this.syncManager.initialDataLoaded()) { } else if (this.application.isDatabaseLoaded()) {
if (!tag.isSmartTag() || tag.content.isAllTag) { if (!tag.isSmartTag() || tag.content.isAllTag) {
this.createNewNote(); this.createNewNote();
} else if ( } else if (
@@ -279,7 +266,7 @@ class NotesCtrl extends PureCtrl {
} }
const previousNote = this.state.selectedNote; const previousNote = this.state.selectedNote;
if (previousNote && previousNote.dummy) { if (previousNote && previousNote.dummy) {
this.modelManager.removeItemLocally(previousNote); this.application.removeItemLocally({previousNote});
this.removeNoteFromList(previousNote); this.removeNoteFromList(previousNote);
} }
await this.setState({ await this.setState({
@@ -292,8 +279,7 @@ class NotesCtrl extends PureCtrl {
this.selectedIndex = Math.max(0, this.displayableNotes().indexOf(note)); this.selectedIndex = Math.max(0, this.displayableNotes().indexOf(note));
if (note.content.conflict_of) { if (note.content.conflict_of) {
note.content.conflict_of = null; note.content.conflict_of = null;
this.modelManager.setItemDirty(note); this.application.saveItem({item: note});
this.syncManager.sync();
} }
if (this.isFiltering()) { if (this.isFiltering()) {
this.desktopManager.searchText(this.state.noteFilter.text); this.desktopManager.searchText(this.state.noteFilter.text);
@@ -536,8 +522,8 @@ class NotesCtrl extends PureCtrl {
return; return;
} }
const title = "Note" + (this.state.notes ? (" " + (this.state.notes.length + 1)) : ""); const title = "Note" + (this.state.notes ? (" " + (this.state.notes.length + 1)) : "");
const newNote = this.modelManager.createItem({ const newNote = this.application.createItem({
content_type: 'Note', contentType: CONTENT_TYPE_NOTE,
content: { content: {
text: '', text: '',
title: title title: title
@@ -545,19 +531,18 @@ class NotesCtrl extends PureCtrl {
}); });
newNote.client_updated_at = new Date(); newNote.client_updated_at = new Date();
newNote.dummy = true; newNote.dummy = true;
this.modelManager.addItem(newNote); this.application.setItemNeedsSync({item: newNote});
this.modelManager.setItemDirty(newNote);
const selectedTag = this.appState.getSelectedTag(); const selectedTag = this.appState.getSelectedTag();
if (!selectedTag.isSmartTag()) { if (!selectedTag.isSmartTag()) {
selectedTag.addItemAsRelationship(newNote); selectedTag.addItemAsRelationship(newNote);
this.modelManager.setItemDirty(selectedTag); this.application.setItemNeedsSync({ item: selectedTag });
} }
this.selectNote(newNote); this.selectNote(newNote);
} }
isFiltering() { isFiltering() {
return this.state.noteFilter.text && return this.state.noteFilter.text &&
this.state.noteFilter.text.length > 0; this.state.noteFilter.text.length > 0;
} }
async setNoteFilterText(text) { async setNoteFilterText(text) {

View File

@@ -1,9 +1,10 @@
import _ from 'lodash'; import _ from 'lodash';
import { SFAuthManager } from 'snjs'; import { Challenges } from 'snjs';
import { getPlatformString } from '@/utils'; import { getPlatformString } from '@/utils';
import template from '%/root.pug'; import template from '%/root.pug';
import { import {
APP_STATE_EVENT_PANEL_RESIZED APP_STATE_EVENT_PANEL_RESIZED,
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '@/state'; } from '@/state';
import { import {
PANEL_NAME_NOTES, PANEL_NAME_NOTES,
@@ -15,257 +16,188 @@ import {
StringSyncException StringSyncException
} from '@/strings'; } from '@/strings';
/** How often to automatically sync, in milliseconds */ class RootCtrl extends PureCtrl {
const AUTO_SYNC_INTERVAL = 30000;
class RootCtrl {
/* @ngInject */ /* @ngInject */
constructor( constructor(
$location, $location,
$rootScope, $rootScope,
$scope,
$timeout, $timeout,
alertManager, application,
appState, appState,
authManager, databaseManager,
dbManager, lockManager,
modelManager,
passcodeManager,
preferencesManager, preferencesManager,
themeManager /** Unused below, required to load globally */, themeManager /** Unused below, required to load globally */,
statusManager, statusManager,
storageManager,
syncManager,
) { ) {
this.$rootScope = $rootScope; super($timeout);
this.$scope = $scope;
this.$location = $location; this.$location = $location;
this.$rootScope = $rootScope;
this.$timeout = $timeout; this.$timeout = $timeout;
this.dbManager = dbManager; this.application = application;
this.syncManager = syncManager;
this.statusManager = statusManager;
this.storageManager = storageManager;
this.appState = appState; this.appState = appState;
this.authManager = authManager; this.databaseManager = databaseManager;
this.modelManager = modelManager; this.lockManager = lockManager;
this.alertManager = alertManager;
this.preferencesManager = preferencesManager; this.preferencesManager = preferencesManager;
this.passcodeManager = passcodeManager; this.statusManager = statusManager;
this.platformString = getPlatformString();
this.state = {
needsUnlock: false,
appClass: ''
};
this.defineRootScopeFunctions(); this.loadApplication();
this.handleAutoSignInFromParams(); this.handleAutoSignInFromParams();
this.initializeStorageManager();
this.addAppStateObserver(); this.addAppStateObserver();
this.addDragDropHandlers(); this.addDragDropHandlers();
this.defaultLoad();
} }
defineRootScopeFunctions() { async loadApplication() {
this.$rootScope.lockApplication = () => { this.application.prepareForLaunch({
/** Reloading wipes current objects from memory */ callbacks: {
window.location.reload(); authChallengeResponses: async (challenges) => {
}; if (challenges.includes(Challenges.LocalPasscode)) {
this.setState({ needsUnlock: true });
this.$rootScope.safeApply = (fn) => { }
const phase = this.$scope.$root.$$phase; }
if(phase === '$apply' || phase === '$digest') {
this.$scope.$eval(fn);
} else {
this.$scope.$apply(fn);
} }
}; });
await this.application.launch();
this.setState({ needsUnlock: false });
await this.openDatabase();
this.preferencesManager.load();
this.addSyncStatusObserver();
this.addSyncEventHandler();
} }
defaultLoad() { onUpdateAvailable() {
this.$scope.platform = getPlatformString(); this.$rootScope.$broadcast('new-update-available');
};
if(this.passcodeManager.isLocked()) {
this.$scope.needsUnlock = true;
} else {
this.loadAfterUnlock();
}
this.$scope.onSuccessfulUnlock = () => {
this.$timeout(() => {
this.$scope.needsUnlock = false;
this.loadAfterUnlock();
});
};
this.$scope.onUpdateAvailable = () => {
this.$rootScope.$broadcast('new-update-available');
};
}
initializeStorageManager() {
this.storageManager.initialize(
this.passcodeManager.hasPasscode(),
this.authManager.isEphemeralSession()
);
}
addAppStateObserver() { addAppStateObserver() {
this.appState.addObserver((eventName, data) => { this.appState.addObserver(async (eventName, data) => {
if(eventName === APP_STATE_EVENT_PANEL_RESIZED) { if (eventName === APP_STATE_EVENT_PANEL_RESIZED) {
if(data.panel === PANEL_NAME_NOTES) { if (data.panel === PANEL_NAME_NOTES) {
this.notesCollapsed = data.collapsed; this.notesCollapsed = data.collapsed;
} }
if(data.panel === PANEL_NAME_TAGS) { if (data.panel === PANEL_NAME_TAGS) {
this.tagsCollapsed = data.collapsed; this.tagsCollapsed = data.collapsed;
} }
let appClass = ""; let appClass = "";
if(this.notesCollapsed) { appClass += "collapsed-notes"; } if (this.notesCollapsed) { appClass += "collapsed-notes"; }
if(this.tagsCollapsed) { appClass += " collapsed-tags"; } if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.$scope.appClass = appClass; this.setState({ appClass });
} else if (eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
if (!(await this.application.isPasscodeLocked())) {
this.application.sync();
}
} }
}); });
} }
loadAfterUnlock() { async openDatabase() {
this.openDatabase(); this.databaseManager.setLocked(false);
this.authManager.loadInitialData(); this.databaseManager.openDatabase({
this.preferencesManager.load();
this.addSyncStatusObserver();
this.configureKeyRequestHandler();
this.addSyncEventHandler();
this.addSignOutObserver();
this.loadLocalData();
}
openDatabase() {
this.dbManager.setLocked(false);
this.dbManager.openDatabase({
onUpgradeNeeded: () => { onUpgradeNeeded: () => {
/** /**
* New database, delete syncToken so that items * New database/database wiped, delete syncToken so that items
* can be refetched entirely from server * can be refetched entirely from server
*/ */
this.syncManager.clearSyncToken(); this.application.syncManager.clearSyncPositionTokens();
this.syncManager.sync(); this.application.sync();
} }
}); });
} }
addSyncStatusObserver() { // addSyncStatusObserver() {
this.syncStatusObserver = this.syncManager.registerSyncStatusObserver((status) => { // this.syncStatusObserver = this.syncManager.registerSyncStatusObserver((status) => {
if(status.retrievedCount > 20) { // if (status.retrievedCount > 20) {
const text = `Downloading ${status.retrievedCount} items. Keep app open.`; // const text = `Downloading ${status.retrievedCount} items. Keep app open.`;
this.syncStatus = this.statusManager.replaceStatusWithString( // this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus, // this.syncStatus,
text // text
); // );
this.showingDownloadStatus = true; // this.showingDownloadStatus = true;
} else if(this.showingDownloadStatus) { // } else if (this.showingDownloadStatus) {
this.showingDownloadStatus = false; // this.showingDownloadStatus = false;
const text = "Download Complete."; // const text = "Download Complete.";
this.syncStatus = this.statusManager.replaceStatusWithString( // this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus, // this.syncStatus,
text // text
); // );
setTimeout(() => { // setTimeout(() => {
this.syncStatus = this.statusManager.removeStatus(this.syncStatus); // this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
}, 2000); // }, 2000);
} else if(status.total > 20) { // } else if (status.total > 20) {
this.uploadSyncStatus = this.statusManager.replaceStatusWithString( // this.uploadSyncStatus = this.statusManager.replaceStatusWithString(
this.uploadSyncStatus, // this.uploadSyncStatus,
`Syncing ${status.current}/${status.total} items...` // `Syncing ${status.current}/${status.total} items...`
); // );
} else if(this.uploadSyncStatus) { // } else if (this.uploadSyncStatus) {
this.uploadSyncStatus = this.statusManager.removeStatus( // this.uploadSyncStatus = this.statusManager.removeStatus(
this.uploadSyncStatus // this.uploadSyncStatus
); // );
} // }
}); // });
} // }
configureKeyRequestHandler() { // addSyncEventHandler() {
this.syncManager.setKeyRequestHandler(async () => { // let lastShownDate;
const offline = this.authManager.offline(); // this.syncManager.addEventHandler((syncEvent, data) => {
const authParams = ( // this.$rootScope.$broadcast(
offline // syncEvent,
? this.passcodeManager.passcodeAuthParams() // data || {}
: await this.authManager.getAuthParams() // );
); // if (syncEvent === 'sync-session-invalid') {
const keys = offline // /** Don't show repeatedly; at most 30 seconds in between */
? this.passcodeManager.keys() // const SHOW_INTERVAL = 30;
: await this.authManager.keys(); // const lastShownSeconds = (new Date() - lastShownDate) / 1000;
return { // if (!lastShownDate || lastShownSeconds > SHOW_INTERVAL) {
keys: keys, // lastShownDate = new Date();
offline: offline, // setTimeout(() => {
auth_params: authParams // this.alertManager.alert({
}; // text: STRING_SESSION_EXPIRED
}); // });
} // }, 500);
// }
// } else if (syncEvent === 'sync-exception') {
// this.alertManager.alert({
// text: StringSyncException(data)
// });
// }
// });
// }
addSyncEventHandler() { // loadLocalData() {
let lastShownDate; // const encryptionEnabled = this.application.getUser || this.application.hasPasscode();
this.syncManager.addEventHandler((syncEvent, data) => { // this.syncStatus = this.statusManager.addStatusFromString(
this.$rootScope.$broadcast( // encryptionEnabled ? "Decrypting items..." : "Loading items..."
syncEvent, // );
data || {} // const incrementalCallback = (current, total) => {
); // const notesString = `${current}/${total} items...`;
if(syncEvent === 'sync-session-invalid') { // const status = encryptionEnabled
/** Don't show repeatedly; at most 30 seconds in between */ // ? `Decrypting ${notesString}`
const SHOW_INTERVAL = 30; // : `Loading ${notesString}`;
const lastShownSeconds = (new Date() - lastShownDate) / 1000; // this.syncStatus = this.statusManager.replaceStatusWithString(
if(!lastShownDate || lastShownSeconds > SHOW_INTERVAL) { // this.syncStatus,
lastShownDate = new Date(); // status
setTimeout(() => { // );
this.alertManager.alert({ // };
text: STRING_SESSION_EXPIRED // this.syncManager.loadLocalItems({ incrementalCallback }).then(() => {
}); // this.$timeout(() => {
}, 500); // this.$rootScope.$broadcast("initial-data-loaded");
} // this.syncStatus = this.statusManager.replaceStatusWithString(
} else if(syncEvent === 'sync-exception') { // this.syncStatus,
this.alertManager.alert({ // "Syncing..."
text: StringSyncException(data) // );
}); // this.syncManager.sync({
} // checkIntegrity: true
}); // }).then(() => {
} // this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
// });
loadLocalData() { // });
const encryptionEnabled = this.authManager.user || this.passcodeManager.hasPasscode(); // });
this.syncStatus = this.statusManager.addStatusFromString( // }
encryptionEnabled ? "Decrypting items..." : "Loading items..."
);
const incrementalCallback = (current, total) => {
const notesString = `${current}/${total} items...`;
const status = encryptionEnabled
? `Decrypting ${notesString}`
: `Loading ${notesString}`;
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
status
);
};
this.syncManager.loadLocalItems({incrementalCallback}).then(() => {
this.$timeout(() => {
this.$rootScope.$broadcast("initial-data-loaded");
this.syncStatus = this.statusManager.replaceStatusWithString(
this.syncStatus,
"Syncing..."
);
this.syncManager.sync({
performIntegrityCheck: true
}).then(() => {
this.syncStatus = this.statusManager.removeStatus(this.syncStatus);
});
setInterval(() => {
this.syncManager.sync();
}, AUTO_SYNC_INTERVAL);
});
});
}
addSignOutObserver() {
this.authManager.addEventHandler((event) => {
if(event === SFAuthManager.DidSignOutEvent) {
this.modelManager.handleSignout();
this.syncManager.handleSignout();
}
});
}
addDragDropHandlers() { addDragDropHandlers() {
/** /**
@@ -280,9 +212,9 @@ class RootCtrl {
}, false); }, false);
window.addEventListener('drop', (event) => { window.addEventListener('drop', (event) => {
if(event.dataTransfer.files.length > 0) { if (event.dataTransfer.files.length > 0) {
event.preventDefault(); event.preventDefault();
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_DEFAULT_FILE_ERROR text: STRING_DEFAULT_FILE_ERROR
}); });
} }
@@ -294,39 +226,33 @@ class RootCtrl {
return this.$location.search()[key]; return this.$location.search()[key];
}; };
const autoSignInFromParams = async () => { const autoSignInFromParams = async () => {
const server = urlParam('server'); const server = urlParam('server');
const email = urlParam('email'); const email = urlParam('email');
const pw = urlParam('pw'); const pw = urlParam('pw');
if(!this.authManager.offline()) { if (!this.application.getUser()) {
if( if (
await this.syncManager.getServerURL() === server await this.application.getHost() === server
&& this.authManager.user.email === email && this.application.getUser().email === email
) { ) {
/** Already signed in, return */ /** Already signed in, return */
// eslint-disable-next-line no-useless-return // eslint-disable-next-line no-useless-return
return; return;
} else { } else {
/** Sign out */ /** Sign out */
this.authManager.signout(true).then(() => { await this.application.signOut();
window.location.reload(); await this.application.restart();
});
} }
} else { } else {
this.authManager.login( await this.application.setHost(server);
server, this.application.signIn({
email, email: email,
pw, password: pw,
false,
false,
{}
).then((response) => {
window.location.reload();
}); });
} }
}; };
if(urlParam('server')) { if (urlParam('server')) {
autoSignInFromParams(); autoSignInFromParams();
} }
} }
@@ -336,5 +262,8 @@ export class Root {
constructor() { constructor() {
this.template = template; this.template = template;
this.controller = RootCtrl; this.controller = RootCtrl;
this.replace = true;
this.controllerAs = 'self';
this.bindToController = true;
} }
} }

View File

@@ -1,4 +1,4 @@
import { SNNote, SNSmartTag } from 'snjs'; import { SNNote, SNSmartTag, CONTENT_TYPE_TAG, CONTENT_TYPE_SMART_TAG } from 'snjs';
import template from '%/tags.pug'; import template from '%/tags.pug';
import { import {
APP_STATE_EVENT_PREFERENCES_CHANGED, APP_STATE_EVENT_PREFERENCES_CHANGED,
@@ -14,29 +14,22 @@ class TagsPanelCtrl extends PureCtrl {
constructor( constructor(
$rootScope, $rootScope,
$timeout, $timeout,
alertManager, application,
appState, appState,
componentManager, preferencesManager
modelManager,
preferencesManager,
syncManager,
) { ) {
super($timeout); super($timeout);
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.alertManager = alertManager; this.application = application;
this.appState = appState; this.appState = appState;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.preferencesManager = preferencesManager; this.preferencesManager = preferencesManager;
this.syncManager = syncManager;
this.panelController = {}; this.panelController = {};
this.addSyncEventHandler(); this.beginStreamingItems();
this.addAppStateObserver(); this.addAppStateObserver();
this.addMappingObserver();
this.loadPreferences(); this.loadPreferences();
this.registerComponentHandler(); this.registerComponentHandler();
this.state = { this.state = {
smartTags: this.modelManager.getSmartTags(), smartTags: this.application.getSmartTags(),
noteCounts: {} noteCounts: {}
}; };
} }
@@ -45,18 +38,24 @@ class TagsPanelCtrl extends PureCtrl {
this.selectTag(this.state.smartTags[0]); this.selectTag(this.state.smartTags[0]);
} }
addSyncEventHandler() { beginStreamingItems() {
this.syncManager.addEventHandler(async (syncEvent, data) => { this.application.streamItems({
if ( contentType: CONTENT_TYPE_TAG,
syncEvent === 'local-data-loaded' || stream: async ({items}) => {
syncEvent === 'sync:completed' ||
syncEvent === 'local-data-incremental-load'
) {
await this.setState({ await this.setState({
tags: this.modelManager.tags, tags: this.application.getItems({contentType: CONTENT_TYPE_TAG}),
smartTags: this.modelManager.getSmartTags() smartTags: this.application.getItems({ contentType: CONTENT_TYPE_SMART_TAG }),
}); });
this.reloadNoteCounts(); this.reloadNoteCounts();
if (this.state.selectedTag) {
/** If the selected tag has been deleted, revert to All view. */
const selectedTag = items.find((tag) => {
return tag.uuid === this.state.selectedTag.uuid;
});
if (selectedTag && selectedTag.deleted) {
this.selectTag(this.state.smartTags[0]);
}
}
} }
}); });
} }
@@ -73,27 +72,6 @@ class TagsPanelCtrl extends PureCtrl {
}); });
} }
addMappingObserver() {
this.modelManager.addItemSyncObserver(
'tags-list-tags',
'Tag',
(allItems, validItems, deletedItems, source, sourceKey) => {
this.reloadNoteCounts();
if (!this.state.selectedTag) {
return;
}
/** If the selected tag has been deleted, revert to All view. */
const selectedTag = allItems.find((tag) => {
return tag.uuid === this.state.selectedTag.uuid;
});
if (selectedTag && selectedTag.deleted) {
this.selectTag(this.state.smartTags[0]);
}
}
);
}
reloadNoteCounts() { reloadNoteCounts() {
let allTags = []; let allTags = [];
if (this.state.tags) { if (this.state.tags) {
@@ -140,7 +118,7 @@ class TagsPanelCtrl extends PureCtrl {
} }
registerComponentHandler() { registerComponentHandler() {
this.componentManager.registerHandler({ this.application.componentManager.registerHandler({
identifier: 'tags', identifier: 'tags',
areas: ['tags-list'], areas: ['tags-list'],
activationHandler: (component) => { activationHandler: (component) => {
@@ -152,7 +130,7 @@ class TagsPanelCtrl extends PureCtrl {
actionHandler: (component, action, data) => { actionHandler: (component, action, data) => {
if (action === 'select-item') { if (action === 'select-item') {
if (data.item.content_type === 'Tag') { if (data.item.content_type === 'Tag') {
const tag = this.modelManager.findItem(data.item.uuid); const tag = this.application.findItem({uuid: data.item.uuid});
if (tag) { if (tag) {
this.selectTag(tag); this.selectTag(tag);
} }
@@ -171,14 +149,15 @@ class TagsPanelCtrl extends PureCtrl {
if (tag.isSmartTag()) { if (tag.isSmartTag()) {
Object.defineProperty(tag, 'notes', { Object.defineProperty(tag, 'notes', {
get: () => { get: () => {
return this.modelManager.notesMatchingSmartTag(tag); return this.application.getNotesMatchingSmartTag({
smartTag: tag
});
} }
}); });
} }
if (tag.content.conflict_of) { if (tag.content.conflict_of) {
tag.content.conflict_of = null; tag.content.conflict_of = null;
this.modelManager.setItemDirty(tag); this.application.saveItem({item: tag});
this.syncManager.sync();
} }
this.appState.setSelectedTag(tag); this.appState.setSelectedTag(tag);
} }
@@ -187,8 +166,8 @@ class TagsPanelCtrl extends PureCtrl {
if (this.state.editingTag) { if (this.state.editingTag) {
return; return;
} }
const newTag = this.modelManager.createItem({ const newTag = this.application.createItem({
content_type: 'Tag' contentType: CONTENT_TYPE_TAG
}); });
this.setState({ this.setState({
previousTag: this.state.selectedTag, previousTag: this.state.selectedTag,
@@ -196,7 +175,9 @@ class TagsPanelCtrl extends PureCtrl {
editingTag: newTag, editingTag: newTag,
newTag: newTag newTag: newTag
}); });
this.modelManager.addItem(newTag); /** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.insertItems({items: [newTag]});
} }
tagTitleDidChange(tag) { tagTitleDidChange(tag) {
@@ -215,7 +196,9 @@ class TagsPanelCtrl extends PureCtrl {
tag.title = this.editingOriginalName; tag.title = this.editingOriginalName;
this.editingOriginalName = null; this.editingOriginalName = null;
} else if(this.state.newTag) { } else if(this.state.newTag) {
this.modelManager.removeItemLocally(tag); /** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.removeItemLocally(tag);
this.setState({ this.setState({
selectedTag: this.state.previousTag selectedTag: this.state.previousTag
}); });
@@ -226,20 +209,20 @@ class TagsPanelCtrl extends PureCtrl {
this.editingOriginalName = null; this.editingOriginalName = null;
const matchingTag = this.modelManager.findTag(tag.title); const matchingTag = this.application.findTag({title: tag.title});
const alreadyExists = matchingTag && matchingTag !== tag; const alreadyExists = matchingTag && matchingTag !== tag;
if (this.state.newTag === tag && alreadyExists) { if (this.state.newTag === tag && alreadyExists) {
this.alertManager.alert({ this.application.alertManager.alert({
text: "A tag with this name already exists." text: "A tag with this name already exists."
}); });
this.modelManager.removeItemLocally(tag); /** @todo Should not be accessing internal function */
/** Rely on local state instead of adding to global state */
this.application.modelManager.removeItemLocally(tag);
this.setState({ newTag: null }); this.setState({ newTag: null });
return; return;
} }
this.modelManager.setItemDirty(tag); this.application.saveItem({item: tag});
this.syncManager.sync();
this.modelManager.resortTag(tag);
this.selectTag(tag); this.selectTag(tag);
this.setState({ this.setState({
newTag: null newTag: null
@@ -260,12 +243,11 @@ class TagsPanelCtrl extends PureCtrl {
} }
removeTag(tag) { removeTag(tag) {
this.alertManager.confirm({ this.application.alertManager.confirm({
text: STRING_DELETE_TAG, text: STRING_DELETE_TAG,
destructive: true, destructive: true,
onConfirm: () => { onConfirm: () => {
this.modelManager.setItemToBeDeleted(tag); this.application.deleteItem({item: tag});
this.syncManager.sync();
} }
}); });
} }

View File

@@ -1,7 +1,6 @@
import { isDesktopApplication, isNullOrUndefined } from '@/utils'; import { isDesktopApplication, isNullOrUndefined } from '@/utils';
import { PrivilegesManager } from '@/services/privilegesManager';
import template from '%/directives/account-menu.pug'; import template from '%/directives/account-menu.pug';
import { protocolManager } from 'snjs'; import { ProtectedActions } from 'snjs';
import { PureCtrl } from '@Controllers'; import { PureCtrl } from '@Controllers';
import { import {
STRING_ACCOUNT_MENU_UNCHECK_MERGE, STRING_ACCOUNT_MENU_UNCHECK_MERGE,
@@ -33,56 +32,38 @@ class AccountMenuCtrl extends PureCtrl {
$scope, $scope,
$rootScope, $rootScope,
$timeout, $timeout,
alertManager,
archiveManager, archiveManager,
appVersion, appVersion,
authManager, godService,
modelManager, lockManager,
passcodeManager, application
privilegesManager,
storageManager,
syncManager,
) { ) {
super($timeout); super($timeout);
this.$scope = $scope; this.$scope = $scope;
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.$timeout = $timeout; this.$timeout = $timeout;
this.alertManager = alertManager;
this.archiveManager = archiveManager; this.archiveManager = archiveManager;
this.authManager = authManager; this.godService = godService;
this.modelManager = modelManager; this.lockManager = lockManager;
this.passcodeManager = passcodeManager; this.application = application;
this.privilegesManager = privilegesManager;
this.storageManager = storageManager;
this.syncManager = syncManager;
this.state = { this.state = {
appVersion: 'v' + (window.electronAppVersion || appVersion), appVersion: 'v' + (window.electronAppVersion || appVersion),
user: this.authManager.user, user: this.application.getUser(),
canAddPasscode: !this.authManager.isEphemeralSession(), canAddPasscode: !this.application.isEphemeralSession(),
passcodeAutoLockOptions: this.passcodeManager.getAutoLockIntervalOptions(), passcodeAutoLockOptions: this.lockManager.getAutoLockIntervalOptions(),
formData: { formData: {
mergeLocal: true, mergeLocal: true,
ephemeral: false ephemeral: false
}, },
mutable: { mutable: {}
backupEncrypted: this.encryptedBackupsAvailable()
}
}; };
this.syncStatus = this.syncManager.syncStatus; this.syncStatus = this.application.getSyncStatus();
this.syncManager.getServerURL().then((url) => { this.loadHost();
this.setState({ this.checkForSecurityUpdate();
server: url,
formData: { ...this.state.formData, url: url }
});
});
this.authManager.checkForSecurityUpdate().then((available) => {
this.setState({
securityUpdateAvailable: available
});
});
this.reloadAutoLockInterval(); this.reloadAutoLockInterval();
this.loadBackupsAvailability();
} }
$onInit() { $onInit() {
@@ -97,15 +78,51 @@ class AccountMenuCtrl extends PureCtrl {
}); });
} }
encryptedBackupsAvailable() { async loadHost() {
return !isNullOrUndefined(this.authManager.user) || this.passcodeManager.hasPasscode(); const host = await this.application.getHost();
this.setState({
server: host,
formData: {
...this.state.formData,
url: host
}
});
}
async checkForSecurityUpdate() {
const available = await this.godService.checkForSecurityUpdate();
this.setState({
securityUpdateAvailable: available
});
}
async loadBackupsAvailability() {
const hasUser = !isNullOrUndefined(await this.application.getUser());
const hasPasscode = await this.application.hasPasscode();
const encryptedAvailable = hasUser || hasPasscode;
function encryptionStatusString() {
if (hasUser) {
return STRING_E2E_ENABLED;
} else if (hasPasscode) {
return STRING_LOCAL_ENC_ENABLED;
} else {
return STRING_ENC_NOT_ENABLED;
}
}
this.setState({
encryptionStatusString: encryptionStatusString(),
encryptionEnabled: encryptedAvailable,
mutable: {
...this.state.mutable,
backupEncrypted: encryptedAvailable
}
});
} }
submitMfaForm() { submitMfaForm() {
const params = { this.login();
[this.state.formData.mfa.payload.mfa_key]: this.state.formData.userMfaCode
};
this.login(params);
} }
blurAuthFields() { blurAuthFields() {
@@ -114,9 +131,9 @@ class AccountMenuCtrl extends PureCtrl {
ELEMENT_NAME_AUTH_PASSWORD, ELEMENT_NAME_AUTH_PASSWORD,
ELEMENT_NAME_AUTH_PASSWORD_CONF ELEMENT_NAME_AUTH_PASSWORD_CONF
]; ];
for(const name of names) { for (const name of names) {
const element = document.getElementsByName(name)[0]; const element = document.getElementsByName(name)[0];
if(element) { if (element) {
element.blur(); element.blur();
} }
} }
@@ -143,29 +160,25 @@ class AccountMenuCtrl extends PureCtrl {
}); });
} }
async login(extraParams) { async login() {
/** Prevent a timed sync from occuring while signing in. */
this.syncManager.lockSyncing();
await this.setFormDataState({ await this.setFormDataState({
status: STRING_GENERATING_LOGIN_KEYS, status: STRING_GENERATING_LOGIN_KEYS,
authenticating: true authenticating: true
}); });
const response = await this.authManager.login( const response = await this.application.signIn({
this.state.formData.url, email: this.state.formData.email,
this.state.formData.email, password: this.state.formData.user_password,
this.state.formData.user_password, strict: this.state.formData.strictSignin,
this.state.formData.ephemeral, ephemeral: this.state.formData.ephemeral,
this.state.formData.strictSignin, mfaKeyPath: this.state.formData.mfa.payload.mfa_key,
extraParams mfaCode: this.state.formData.userMfaCode,
); mergeLocal: this.state.formData.mergeLocal
});
const hasError = !response || response.error; const hasError = !response || response.error;
if (!hasError) { if (!hasError) {
await this.onAuthSuccess(); await this.onAuthSuccess();
this.syncManager.unlockSyncing();
this.syncManager.sync({ performIntegrityCheck: true });
return; return;
} }
this.syncManager.unlockSyncing();
await this.setFormDataState({ await this.setFormDataState({
status: null, status: null,
}); });
@@ -174,7 +187,7 @@ class AccountMenuCtrl extends PureCtrl {
: { message: "An unknown error occured." }; : { message: "An unknown error occured." };
if (error.tag === 'mfa-required' || error.tag === 'mfa-invalid') { if (error.tag === 'mfa-required' || error.tag === 'mfa-invalid') {
await this.setFormDataState({ await this.setFormDataState({
showLogin: false, showLogin: false,
mfa: error mfa: error
}); });
@@ -184,7 +197,7 @@ class AccountMenuCtrl extends PureCtrl {
mfa: null mfa: null
}); });
if (error.message) { if (error.message) {
this.alertManager.alert({ this.application.alertManager.alert({
text: error.message text: error.message
}); });
} }
@@ -197,22 +210,22 @@ class AccountMenuCtrl extends PureCtrl {
async register() { async register() {
const confirmation = this.state.formData.password_conf; const confirmation = this.state.formData.password_conf;
if (confirmation !== this.state.formData.user_password) { if (confirmation !== this.state.formData.user_password) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_NON_MATCHING_PASSWORDS text: STRING_NON_MATCHING_PASSWORDS
}); });
return; return;
} }
await this.setFormDataState({ await this.setFormDataState({
confirmPassword: false, confirmPassword: false,
status: STRING_GENERATING_REGISTER_KEYS, status: STRING_GENERATING_REGISTER_KEYS,
authenticating: true authenticating: true
}); });
const response = await this.authManager.register( const response = await this.application.register({
this.state.formData.url, email: this.state.formData.email,
this.state.formData.email, password: this.state.formData.user_password,
this.state.formData.user_password, ephemeral: this.state.formData.ephemeral,
this.state.formData.ephemeral mergeLocal: this.state.formData.mergeLocal
); });
if (!response || response.error) { if (!response || response.error) {
await this.setFormDataState({ await this.setFormDataState({
status: null status: null
@@ -223,18 +236,18 @@ class AccountMenuCtrl extends PureCtrl {
await this.setFormDataState({ await this.setFormDataState({
authenticating: false authenticating: false
}); });
this.alertManager.alert({ this.application.alertManager.alert({
text: error.message text: error.message
}); });
} else { } else {
await this.onAuthSuccess(); await this.onAuthSuccess();
this.syncManager.sync(); this.application.sync();
} }
} }
mergeLocalChanged() { mergeLocalChanged() {
if (!this.state.formData.mergeLocal) { if (!this.state.formData.mergeLocal) {
this.alertManager.confirm({ this.application.alertManager.confirm({
text: STRING_ACCOUNT_MENU_UNCHECK_MERGE, text: STRING_ACCOUNT_MENU_UNCHECK_MERGE,
destructive: true, destructive: true,
onCancel: () => { onCancel: () => {
@@ -249,34 +262,30 @@ class AccountMenuCtrl extends PureCtrl {
async onAuthSuccess() { async onAuthSuccess() {
if (this.state.formData.mergeLocal) { if (this.state.formData.mergeLocal) {
this.$rootScope.$broadcast('major-data-change'); this.$rootScope.$broadcast('major-data-change');
await this.clearDatabaseAndRewriteAllItems({ alternateUuids: true }); await this.rewriteDatabase({ alternateUuids: true });
} else {
this.modelManager.removeAllItemsFromMemory();
await this.storageManager.clearAllModels();
} }
await this.setFormDataState({ await this.setFormDataState({
authenticating: false authenticating: false
}); });
this.syncManager.refreshErroredItems();
this.close(); this.close();
} }
openPasswordWizard(type) { openPasswordWizard(type) {
this.close(); this.close();
this.authManager.presentPasswordWizard(type); this.godService.presentPasswordWizard(type);
} }
async openPrivilegesModal() { async openPrivilegesModal() {
this.close(); this.close();
const run = () => { const run = () => {
this.privilegesManager.presentPrivilegesManagementModal(); this.godService.presentPrivilegesManagementModal();
}; };
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege( const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManagePrivileges ProtectedActions.ManagePrivileges
); );
if (needsPrivilege) { if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionManagePrivileges, ProtectedActions.ManagePrivileges,
() => { () => {
run(); run();
} }
@@ -288,21 +297,21 @@ class AccountMenuCtrl extends PureCtrl {
/** /**
* Allows IndexedDB unencrypted logs to be deleted * Allows IndexedDB unencrypted logs to be deleted
* `clearAllModels` will remove data from backing store, * `clearAllPayloads` will remove data from backing store,
* but not from working memory See: * but not from working memory See:
* https://github.com/standardnotes/desktop/issues/131 * https://github.com/standardnotes/desktop/issues/131
*/ */
async clearDatabaseAndRewriteAllItems({ alternateUuids } = {}) { async rewriteDatabase({ alternateUuids } = {}) {
await this.storageManager.clearAllModels(); await this.application.destroyDatabase();
await this.syncManager.markAllItemsDirtyAndSaveOffline(alternateUuids); await this.application.markAllItemsAsNeedingSync({ alternateUuids });
} }
destroyLocalData() { destroyLocalData() {
this.alertManager.confirm({ this.application.alertManager.confirm({
text: STRING_SIGN_OUT_CONFIRMATION, text: STRING_SIGN_OUT_CONFIRMATION,
destructive: true, destructive: true,
onConfirm: async () => { onConfirm: async () => {
await this.authManager.signout(true); await this.application.signOut();
window.location.reload(); window.location.reload();
} }
}); });
@@ -323,7 +332,7 @@ class AccountMenuCtrl extends PureCtrl {
const data = JSON.parse(e.target.result); const data = JSON.parse(e.target.result);
resolve(data); resolve(data);
} catch (e) { } catch (e) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_INVALID_IMPORT_FILE text: STRING_INVALID_IMPORT_FILE
}); });
} }
@@ -360,12 +369,12 @@ class AccountMenuCtrl extends PureCtrl {
await this.performImport(data, null); await this.performImport(data, null);
} }
}; };
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege( const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManageBackups ProtectedActions.ManageBackups
); );
if (needsPrivilege) { if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionManageBackups, ProtectedActions.ManageBackups,
run run
); );
} else { } else {
@@ -386,47 +395,22 @@ class AccountMenuCtrl extends PureCtrl {
}); });
if (errorCount > 0) { if (errorCount > 0) {
const message = StringImportError({ errorCount: errorCount }); const message = StringImportError({ errorCount: errorCount });
this.alertManager.alert({ this.application.alertManager.alert({
text: message text: message
}); });
} else { } else {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_IMPORT_SUCCESS text: STRING_IMPORT_SUCCESS
}); });
} }
} }
async importJSONData(data, password) { async importJSONData(data, password) {
let errorCount = 0; const { affectedItems, errorCount } = await this.application.importData({
if (data.auth_params) { data: data.items,
const keys = await protocolManager.computeEncryptionKeysForUser( password: password
password, });
data.auth_params for (const item of affectedItems) {
);
try {
const throws = false;
await protocolManager.decryptMultipleItems(data.items, keys, throws);
const items = [];
for (const item of data.items) {
item.enc_item_key = null;
item.auth_hash = null;
if (item.errorDecrypting) {
errorCount++;
} else {
items.push(item);
}
}
data.items = items;
} catch (e) {
this.alertManager.alert({
text: STRING_ERROR_DECRYPTING_IMPORT
});
return;
}
}
const items = await this.modelManager.importItems(data.items);
for (const item of items) {
/** /**
* Don't want to activate any components during import process in * Don't want to activate any components during import process in
* case of exceptions breaking up the import proccess * case of exceptions breaking up the import proccess
@@ -435,8 +419,6 @@ class AccountMenuCtrl extends PureCtrl {
item.active = false; item.active = false;
} }
} }
this.syncManager.sync();
return errorCount; return errorCount;
} }
@@ -445,10 +427,12 @@ class AccountMenuCtrl extends PureCtrl {
} }
notesAndTagsCount() { notesAndTagsCount() {
return this.modelManager.allItemsMatchingTypes([ return this.application.getItems({
'Note', contentType: [
'Tag' 'Note',
]).length; 'Tag'
]
}).length;
} }
encryptionStatusForNotes() { encryptionStatusForNotes() {
@@ -456,32 +440,8 @@ class AccountMenuCtrl extends PureCtrl {
return length + "/" + length + " notes and tags encrypted"; return length + "/" + length + " notes and tags encrypted";
} }
encryptionEnabled() {
return this.passcodeManager.hasPasscode() || !this.authManager.offline();
}
encryptionSource() {
if (!this.authManager.offline()) {
return "Account keys";
} else if (this.passcodeManager.hasPasscode()) {
return "Local Passcode";
} else {
return null;
}
}
encryptionStatusString() {
if (!this.authManager.offline()) {
return STRING_E2E_ENABLED;
} else if (this.passcodeManager.hasPasscode()) {
return STRING_LOCAL_ENC_ENABLED;
} else {
return STRING_ENC_NOT_ENABLED;
}
}
async reloadAutoLockInterval() { async reloadAutoLockInterval() {
const interval = await this.passcodeManager.getAutoLockInterval(); const interval = await this.lockManager.getAutoLockInterval();
this.setState({ this.setState({
selectedAutoLockInterval: interval selectedAutoLockInterval: interval
}); });
@@ -489,15 +449,15 @@ class AccountMenuCtrl extends PureCtrl {
async selectAutoLockInterval(interval) { async selectAutoLockInterval(interval) {
const run = async () => { const run = async () => {
await this.passcodeManager.setAutoLockInterval(interval); await this.lockManager.setAutoLockInterval(interval);
this.reloadAutoLockInterval(); this.reloadAutoLockInterval();
}; };
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege( const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManagePasscode ProtectedActions.ManagePasscode
); );
if (needsPrivilege) { if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionManagePasscode, ProtectedActions.ManagePasscode,
() => { () => {
run(); run();
} }
@@ -508,7 +468,7 @@ class AccountMenuCtrl extends PureCtrl {
} }
hasPasscode() { hasPasscode() {
return this.passcodeManager.hasPasscode(); return this.application.hasPasscode();
} }
addPasscodeClicked() { addPasscodeClicked() {
@@ -520,23 +480,23 @@ class AccountMenuCtrl extends PureCtrl {
submitPasscodeForm() { submitPasscodeForm() {
const passcode = this.state.formData.passcode; const passcode = this.state.formData.passcode;
if (passcode !== this.state.formData.confirmPasscode) { if (passcode !== this.state.formData.confirmPasscode) {
this.alertManager.alert({ this.application.alertManager.alert({
text: STRING_NON_MATCHING_PASSCODES text: STRING_NON_MATCHING_PASSCODES
}); });
return; return;
} }
const func = this.state.formData.changingPasscode const func = this.state.formData.changingPasscode
? this.passcodeManager.changePasscode.bind(this.passcodeManager) ? this.application.changePasscode.bind(this.application)
: this.passcodeManager.setPasscode.bind(this.passcodeManager); : this.application.setPasscode.bind(this.application);
func(passcode, async () => { func(passcode, async () => {
await this.setFormDataState({ await this.setFormDataState({
passcode: null, passcode: null,
confirmPasscode: null, confirmPasscode: null,
showPasscodeForm: false showPasscodeForm: false
}); });
if (await this.authManager.offline()) { if (isNullOrUndefined(await this.application.getUser())) {
this.$rootScope.$broadcast('major-data-change'); this.$rootScope.$broadcast('major-data-change');
this.clearDatabaseAndRewriteAllItems(); this.rewriteDatabase();
} }
}); });
} }
@@ -546,12 +506,12 @@ class AccountMenuCtrl extends PureCtrl {
this.state.formData.changingPasscode = true; this.state.formData.changingPasscode = true;
this.addPasscodeClicked(); this.addPasscodeClicked();
}; };
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege( const needsPrivilege = await this.application.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionManagePasscode ProtectedActions.ManagePasscode
); );
if (needsPrivilege) { if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionManagePasscode, ProtectedActions.ManagePasscode,
run run
); );
} else { } else {
@@ -560,34 +520,24 @@ class AccountMenuCtrl extends PureCtrl {
} }
async removePasscodePressed() { async removePasscodePressed() {
const run = () => { const run = async () => {
const signedIn = !this.authManager.offline(); const signedIn = !isNullOrUndefined(await this.application.getUser());
let message = STRING_REMOVE_PASSCODE_CONFIRMATION; let message = STRING_REMOVE_PASSCODE_CONFIRMATION;
if (!signedIn) { if (!signedIn) {
message += STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM; message += STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM;
} }
this.alertManager.confirm({ this.application.alertManager.confirm({
text: message, text: message,
destructive: true, destructive: true,
onConfirm: () => { onConfirm: () => {
this.passcodeManager.clearPasscode(); this.application.removePasscode();
if (this.authManager.offline()) {
this.syncManager.markAllItemsDirtyAndSaveOffline();
}
} }
}); });
}; };
const needsPrivilege = await this.privilegesManager.actionRequiresPrivilege( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionManagePasscode ProtectedActions.ManagePasscode,
run
); );
if (needsPrivilege) {
this.privilegesManager.presentPrivilegesModal(
PrivilegesManager.ActionManagePasscode,
run
);
} else {
run();
}
} }
isDesktopApplication() { isDesktopApplication() {

View File

@@ -14,15 +14,15 @@ class ComponentViewCtrl {
$scope, $scope,
$rootScope, $rootScope,
$timeout, $timeout,
componentManager, application,
desktopManager, desktopManager,
themeManager themeManager
) { ) {
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.$timeout = $timeout; this.$timeout = $timeout;
this.application = application;
this.themeManager = themeManager; this.themeManager = themeManager;
this.desktopManager = desktopManager; this.desktopManager = desktopManager;
this.componentManager = componentManager;
this.componentValid = true; this.componentValid = true;
$scope.$watch('ctrl.component', (component, prevComponent) => { $scope.$watch('ctrl.component', (component, prevComponent) => {
@@ -52,7 +52,7 @@ class ComponentViewCtrl {
registerComponentHandlers() { registerComponentHandlers() {
this.themeHandlerIdentifier = 'component-view-' + Math.random(); this.themeHandlerIdentifier = 'component-view-' + Math.random();
this.componentManager.registerHandler({ this.application.componentManager.registerHandler({
identifier: this.themeHandlerIdentifier, identifier: this.themeHandlerIdentifier,
areas: ['themes'], areas: ['themes'],
activationHandler: (component) => { activationHandler: (component) => {
@@ -61,7 +61,7 @@ class ComponentViewCtrl {
}); });
this.identifier = 'component-view-' + Math.random(); this.identifier = 'component-view-' + Math.random();
this.componentManager.registerHandler({ this.application.componentManager.registerHandler({
identifier: this.identifier, identifier: this.identifier,
areas: [this.component.area], areas: [this.component.area],
activationHandler: (component) => { activationHandler: (component) => {
@@ -74,7 +74,7 @@ class ComponentViewCtrl {
}, },
actionHandler: (component, action, data) => { actionHandler: (component, action, data) => {
if(action === 'set-size') { if(action === 'set-size') {
this.componentManager.handleSetSizeEvent(component, data); this.application.componentManager.handleSetSizeEvent(component, data);
} }
} }
}); });
@@ -91,7 +91,7 @@ class ComponentViewCtrl {
async reloadComponent() { async reloadComponent() {
this.componentValid = false; this.componentValid = false;
await this.componentManager.reloadComponent(this.component); await this.application.componentManager.reloadComponent(this.component);
this.reloadStatus(); this.reloadStatus();
} }
@@ -124,7 +124,7 @@ class ComponentViewCtrl {
} }
if(this.componentValid !== previouslyValid) { if(this.componentValid !== previouslyValid) {
if(this.componentValid) { if(this.componentValid) {
this.componentManager.reloadComponent(component, true); this.application.componentManager.reloadComponent(component, true);
} }
} }
if(this.expired && doManualReload) { if(this.expired && doManualReload) {
@@ -140,7 +140,7 @@ class ComponentViewCtrl {
if(!this.component.active) { if(!this.component.active) {
return; return;
} }
const iframe = this.componentManager.iframeForComponent( const iframe = this.application.componentManager.iframeForComponent(
this.component this.component
); );
if(!iframe) { if(!iframe) {
@@ -186,7 +186,7 @@ class ComponentViewCtrl {
} catch (e) {} } catch (e) {}
} }
this.$timeout.cancel(this.loadTimeout); this.$timeout.cancel(this.loadTimeout);
await this.componentManager.registerComponentWindow( await this.application.componentManager.registerComponentWindow(
this.component, this.component,
iframe.contentWindow iframe.contentWindow
); );
@@ -201,13 +201,13 @@ class ComponentViewCtrl {
componentValueDidSet(component, prevComponent) { componentValueDidSet(component, prevComponent) {
const dontSync = true; const dontSync = true;
if(prevComponent && component !== prevComponent) { if(prevComponent && component !== prevComponent) {
this.componentManager.deactivateComponent( this.application.componentManager.deactivateComponent(
prevComponent, prevComponent,
dontSync dontSync
); );
} }
if(component) { if(component) {
this.componentManager.activateComponent( this.application.componentManager.activateComponent(
component, component,
dontSync dontSync
); );
@@ -239,17 +239,17 @@ class ComponentViewCtrl {
} }
getUrl() { getUrl() {
const url = this.componentManager.urlForComponent(this.component); const url = this.application.componentManager.urlForComponent(this.component);
this.component.runningLocally = (url === this.component.local_url); this.component.runningLocally = (url === this.component.local_url);
return url; return url;
} }
destroy() { destroy() {
this.componentManager.deregisterHandler(this.themeHandlerIdentifier); this.application.componentManager.deregisterHandler(this.themeHandlerIdentifier);
this.componentManager.deregisterHandler(this.identifier); this.application.componentManager.deregisterHandler(this.identifier);
if(this.component && !this.manualDealloc) { if(this.component && !this.manualDealloc) {
const dontSync = true; const dontSync = true;
this.componentManager.deactivateComponent(this.component, dontSync); this.application.componentManager.deactivateComponent(this.component, dontSync);
} }
this.desktopManager.deregisterUpdateObserver(this.updateObserver); this.desktopManager.deregisterUpdateObserver(this.updateObserver);

View File

@@ -4,16 +4,12 @@ class ConflictResolutionCtrl {
/* @ngInject */ /* @ngInject */
constructor( constructor(
$element, $element,
alertManager,
archiveManager, archiveManager,
modelManager, application
syncManager
) { ) {
this.$element = $element; this.$element = $element;
this.alertManager = alertManager; this.application = application;
this.archiveManager = archiveManager; this.archiveManager = archiveManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
} }
$onInit() { $onInit() {
@@ -31,35 +27,31 @@ class ConflictResolutionCtrl {
} }
keepItem1() { keepItem1() {
this.alertManager.confirm({ this.application.alertManager.confirm({
text: `Are you sure you want to delete the item on the right?`, text: `Are you sure you want to delete the item on the right?`,
destructive: true, destructive: true,
onConfirm: () => { onConfirm: () => {
this.modelManager.setItemToBeDeleted(this.item2); this.application.deleteItem({item: this.item2});
this.syncManager.sync().then(() => { this.triggerCallback();
this.applyCallback();
});
this.dismiss(); this.dismiss();
} }
}); });
} }
keepItem2() { keepItem2() {
this.alertManager.confirm({ this.application.alertManager.confirm({
text: `Are you sure you want to delete the item on the left?`, text: `Are you sure you want to delete the item on the left?`,
destructive: true, destructive: true,
onConfirm: () => { onConfirm: () => {
this.modelManager.setItemToBeDeleted(this.item1); this.application.deleteItem({item: this.item1});
this.syncManager.sync().then(() => { this.triggerCallback();
this.applyCallback();
});
this.dismiss(); this.dismiss();
} }
}); });
} }
keepBoth() { keepBoth() {
this.applyCallback(); this.triggerCallback();
this.dismiss(); this.dismiss();
} }
@@ -70,7 +62,7 @@ class ConflictResolutionCtrl {
); );
} }
applyCallback() { triggerCallback() {
this.callback && this.callback(); this.callback && this.callback();
} }

View File

@@ -6,22 +6,17 @@ class EditorMenuCtrl extends PureCtrl {
/* @ngInject */ /* @ngInject */
constructor( constructor(
$timeout, $timeout,
componentManager, application
modelManager,
syncManager,
) { ) {
super($timeout); super($timeout);
this.$timeout = $timeout; this.application = application;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
this.state = { this.state = {
isDesktop: isDesktopApplication() isDesktop: isDesktopApplication()
}; };
} }
$onInit() { $onInit() {
const editors = this.componentManager.componentsForArea('editor-editor') const editors = this.application.componentManager.componentsForArea('editor-editor')
.sort((a, b) => { .sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
}); });
@@ -36,8 +31,7 @@ class EditorMenuCtrl extends PureCtrl {
if(component) { if(component) {
if(component.content.conflict_of) { if(component.content.conflict_of) {
component.content.conflict_of = null; component.content.conflict_of = null;
this.modelManager.setItemDirty(component, true); this.application.saveItem({item: component});
this.syncManager.sync();
} }
} }
this.$timeout(() => { this.$timeout(() => {
@@ -58,16 +52,15 @@ class EditorMenuCtrl extends PureCtrl {
} }
makeEditorDefault(component) { makeEditorDefault(component) {
const currentDefault = this.componentManager const currentDefault = this.application.componentManager
.componentsForArea('editor-editor') .componentsForArea('editor-editor')
.filter((e) => e.isDefaultEditor())[0]; .filter((e) => e.isDefaultEditor())[0];
if(currentDefault) { if(currentDefault) {
currentDefault.setAppDataItem('defaultEditor', false); currentDefault.setAppDataItem('defaultEditor', false);
this.modelManager.setItemDirty(currentDefault); this.application.setItemsNeedsSync({item: currentDefault});
} }
component.setAppDataItem('defaultEditor', true); component.setAppDataItem('defaultEditor', true);
this.modelManager.setItemDirty(component); this.application.saveItem({ item: component });
this.syncManager.sync();
this.setState({ this.setState({
defaultEditor: component defaultEditor: component
}); });
@@ -75,8 +68,7 @@ class EditorMenuCtrl extends PureCtrl {
removeEditorDefault(component) { removeEditorDefault(component) {
component.setAppDataItem('defaultEditor', false); component.setAppDataItem('defaultEditor', false);
this.modelManager.setItemDirty(component); this.application.saveItem({ item: component });
this.syncManager.sync();
this.setState({ this.setState({
defaultEditor: null defaultEditor: null
}); });

View File

@@ -1,6 +1,6 @@
import { protocolManager } from 'snjs';
import template from '%/directives/password-wizard.pug'; import template from '%/directives/password-wizard.pug';
import { STRING_FAILED_PASSWORD_CHANGE } from '@/strings'; import { STRING_FAILED_PASSWORD_CHANGE } from '@/strings';
import { isNullOrUndefined } from '../../utils';
const DEFAULT_CONTINUE_TITLE = "Continue"; const DEFAULT_CONTINUE_TITLE = "Continue";
const Steps = { const Steps = {
@@ -18,25 +18,17 @@ class PasswordWizardCtrl {
$element, $element,
$scope, $scope,
$timeout, $timeout,
alertManager, archiveManager
archiveManager,
authManager,
modelManager,
syncManager,
) { ) {
this.$element = $element; this.$element = $element;
this.$timeout = $timeout; this.$timeout = $timeout;
this.$scope = $scope; this.$scope = $scope;
this.alertManager = alertManager;
this.archiveManager = archiveManager; this.archiveManager = archiveManager;
this.authManager = authManager;
this.modelManager = modelManager;
this.syncManager = syncManager;
this.registerWindowUnloadStopper(); this.registerWindowUnloadStopper();
} }
$onInit() { $onInit() {
this.syncStatus = this.syncManager.syncStatus; this.syncStatus = this.application.getSyncStatus();
this.formData = {}; this.formData = {};
this.configureDefaults(); this.configureDefaults();
} }
@@ -139,20 +131,11 @@ class PasswordWizardCtrl {
this.formData.status = "Unable to process your password. Please try again."; this.formData.status = "Unable to process your password. Please try again.";
return; return;
} }
this.formData.status = "Encrypting and syncing data with new keys..."; this.lockContinue = false;
if (this.changePassword) {
const syncSuccess = await this.resyncData(); this.formData.status = "Successfully changed password.";
this.formData.statusError = !syncSuccess; } else if (this.securityUpdate) {
this.formData.processing = !syncSuccess; this.formData.status = "Successfully performed security update.";
if (syncSuccess) {
this.lockContinue = false;
if (this.changePassword) {
this.formData.status = "Successfully changed password and synced all items.";
} else if (this.securityUpdate) {
this.formData.status = "Successfully performed security update and synced all items.";
}
} else {
this.formData.status = STRING_FAILED_PASSWORD_CHANGE;
} }
} }
@@ -160,28 +143,28 @@ class PasswordWizardCtrl {
const currentPassword = this.formData.currentPassword; const currentPassword = this.formData.currentPassword;
const newPass = this.securityUpdate ? currentPassword : this.formData.newPassword; const newPass = this.securityUpdate ? currentPassword : this.formData.newPassword;
if (!currentPassword || currentPassword.length === 0) { if (!currentPassword || currentPassword.length === 0) {
this.alertManager.alert({ this.application.alertManager.alert({
text: "Please enter your current password." text: "Please enter your current password."
}); });
return false; return false;
} }
if (this.changePassword) { if (this.changePassword) {
if (!newPass || newPass.length === 0) { if (!newPass || newPass.length === 0) {
this.alertManager.alert({ this.application.alertManager.alert({
text: "Please enter a new password." text: "Please enter a new password."
}); });
return false; return false;
} }
if (newPass !== this.formData.newPasswordConfirmation) { if (newPass !== this.formData.newPasswordConfirmation) {
this.alertManager.alert({ this.application.alertManager.alert({
text: "Your new password does not match its confirmation." text: "Your new password does not match its confirmation."
}); });
this.formData.status = null; this.formData.status = null;
return false; return false;
} }
} }
if (!this.authManager.user.email) { if (!this.application.getUser().email) {
this.alertManager.alert({ this.application.alertManager.alert({
text: "We don't have your email stored. Please log out then log back in to fix this issue." text: "We don't have your email stored. Please log out then log back in to fix this issue."
}); });
this.formData.status = null; this.formData.status = null;
@@ -189,61 +172,31 @@ class PasswordWizardCtrl {
} }
/** Validate current password */ /** Validate current password */
const authParams = await this.authManager.getAuthParams(); const key = await this.application.validateAccountPassword({
const password = this.formData.currentPassword; password: this.formData.currentPassword
const keys = await protocolManager.computeEncryptionKeysForUser( });
password, if (key) {
authParams this.currentServerPassword = key.serverPassword;
);
const success = keys.mk === (await this.authManager.keys()).mk;
if (success) {
this.currentServerPw = keys.pw;
} else { } else {
this.alertManager.alert({ this.application.alertManager.alert({
text: "The current password you entered is not correct. Please try again." text: "The current password you entered is not correct. Please try again."
}); });
} }
return success; return !isNullOrUndefined(key);
}
async resyncData() {
await this.modelManager.setAllItemsDirty();
const response = await this.syncManager.sync();
if (!response || response.error) {
this.alertManager.alert({
text: STRING_FAILED_PASSWORD_CHANGE
});
return false;
} else {
return true;
}
} }
async processPasswordChange() { async processPasswordChange() {
const newUserPassword = this.securityUpdate const newPassword = this.securityUpdate
? this.formData.currentPassword ? this.formData.currentPassword
: this.formData.newPassword; : this.formData.newPassword;
const currentServerPw = this.currentServerPw;
const results = await protocolManager.generateInitialKeysAndAuthParamsForUser( const response = await this.application.changePassword({
this.authManager.user.email, email: this.application.getUser().email,
newUserPassword currentPassword: this.formData.currentPassword,
); newPassword: newPassword
const newKeys = results.keys; });
const newAuthParams = results.authParams;
/**
* Perform a sync beforehand to pull in any last minutes changes before we change
* the encryption key (and thus cant decrypt new changes).
*/
await this.syncManager.sync();
const response = await this.authManager.changePassword(
await this.syncManager.getServerURL(),
this.authManager.user.email,
currentServerPw,
newKeys,
newAuthParams
);
if (response.error) { if (response.error) {
this.alertManager.alert({ this.application.alertManager.alert({
text: response.error.message text: response.error.message
? response.error.message ? response.error.message
: "There was an error changing your password. Please try again." : "There was an error changing your password. Please try again."
@@ -260,7 +213,7 @@ class PasswordWizardCtrl {
dismiss() { dismiss() {
if (this.lockContinue) { if (this.lockContinue) {
this.alertManager.alert({ this.application.alertManager.alert({
text: "Cannot close window until pending tasks are complete." text: "Cannot close window until pending tasks are complete."
}); });
} else { } else {

View File

@@ -5,22 +5,24 @@ class PrivilegesAuthModalCtrl {
constructor( constructor(
$element, $element,
$timeout, $timeout,
privilegesManager, application
) { ) {
this.$element = $element; this.$element = $element;
this.$timeout = $timeout; this.$timeout = $timeout;
this.privilegesManager = privilegesManager; this.application = application;
} }
$onInit() { $onInit() {
this.authParameters = {}; this.authParameters = {};
this.sessionLengthOptions = this.privilegesManager.getSessionLengthOptions(); this.sessionLengthOptions = this.application.privilegesManager.getSessionLengthOptions();
this.privilegesManager.getSelectedSessionLength().then((length) => { this.application.privilegesManager.getSelectedSessionLength()
.then((length) => {
this.$timeout(() => { this.$timeout(() => {
this.selectedSessionLength = length; this.selectedSessionLength = length;
}); });
}); });
this.privilegesManager.netCredentialsForAction(this.action).then((credentials) => { this.application.privilegesManager.netCredentialsForAction(this.action)
.then((credentials) => {
this.$timeout(() => { this.$timeout(() => {
this.requiredCredentials = credentials.sort(); this.requiredCredentials = credentials.sort();
}); });
@@ -32,7 +34,7 @@ class PrivilegesAuthModalCtrl {
} }
promptForCredential(credential) { promptForCredential(credential) {
return this.privilegesManager.displayInfoForCredential(credential).prompt; return this.application.privilegesManager.displayInfoForCredential(credential).prompt;
} }
cancel() { cancel() {
@@ -65,13 +67,13 @@ class PrivilegesAuthModalCtrl {
if (!this.validate()) { if (!this.validate()) {
return; return;
} }
const result = await this.privilegesManager.authenticateAction( const result = await this.application.privilegesManager.authenticateAction(
this.action, this.action,
this.authParameters this.authParameters
); );
this.$timeout(() => { this.$timeout(() => {
if (result.success) { if (result.success) {
this.privilegesManager.setSessionLength(this.selectedSessionLength); this.application.privilegesManager.setSessionLength(this.selectedSessionLength);
this.onSuccess(); this.onSuccess();
this.dismiss(); this.dismiss();
} else { } else {

View File

@@ -6,20 +6,18 @@ class PrivilegesManagementModalCtrl {
constructor( constructor(
$timeout, $timeout,
$element, $element,
privilegesManager, application
authManager,
passcodeManager,
) { ) {
this.$element = $element; this.$element = $element;
this.$timeout = $timeout; this.$timeout = $timeout;
this.privilegesManager = privilegesManager; this.application = application;
this.hasPasscode = passcodeManager.hasPasscode(); this.hasPasscode = application.hasPasscode();
this.hasAccount = !authManager.offline(); this.hasAccount = !application.noUser();
this.reloadPrivileges(); this.reloadPrivileges();
} }
displayInfoForCredential(credential) { displayInfoForCredential(credential) {
const info = this.privilegesManager.displayInfoForCredential(credential); const info = this.application.privilegesManager.displayInfoForCredential(credential);
if (credential === PrivilegesManager.CredentialLocalPasscode) { if (credential === PrivilegesManager.CredentialLocalPasscode) {
info.availability = this.hasPasscode; info.availability = this.hasPasscode;
} else if (credential === PrivilegesManager.CredentialAccountPassword) { } else if (credential === PrivilegesManager.CredentialAccountPassword) {
@@ -31,7 +29,7 @@ class PrivilegesManagementModalCtrl {
} }
displayInfoForAction(action) { displayInfoForAction(action) {
return this.privilegesManager.displayInfoForAction(action).label; return this.application.privilegesManager.displayInfoForAction(action).label;
} }
isCredentialRequiredForAction(action, credential) { isCredentialRequiredForAction(action, credential) {
@@ -42,21 +40,21 @@ class PrivilegesManagementModalCtrl {
} }
async clearSession() { async clearSession() {
await this.privilegesManager.clearSession(); await this.application.privilegesManager.clearSession();
this.reloadPrivileges(); this.reloadPrivileges();
} }
async reloadPrivileges() { async reloadPrivileges() {
this.availableActions = this.privilegesManager.getAvailableActions(); this.availableActions = this.application.privilegesManager.getAvailableActions();
this.availableCredentials = this.privilegesManager.getAvailableCredentials(); this.availableCredentials = this.application.privilegesManager.getAvailableCredentials();
const sessionEndDate = await this.privilegesManager.getSessionExpirey(); const sessionEndDate = await this.application.privilegesManager.getSessionExpirey();
this.sessionExpirey = sessionEndDate.toLocaleString(); this.sessionExpirey = sessionEndDate.toLocaleString();
this.sessionExpired = new Date() >= sessionEndDate; this.sessionExpired = new Date() >= sessionEndDate;
this.credentialDisplayInfo = {}; this.credentialDisplayInfo = {};
for (const cred of this.availableCredentials) { for (const cred of this.availableCredentials) {
this.credentialDisplayInfo[cred] = this.displayInfoForCredential(cred); this.credentialDisplayInfo[cred] = this.displayInfoForCredential(cred);
} }
const privs = await this.privilegesManager.getPrivileges(); const privs = await this.application.privilegesManager.getPrivileges();
this.$timeout(() => { this.$timeout(() => {
this.privileges = privs; this.privileges = privs;
}); });
@@ -64,7 +62,7 @@ class PrivilegesManagementModalCtrl {
checkboxValueChanged(action, credential) { checkboxValueChanged(action, credential) {
this.privileges.toggleCredentialForAction(action, credential); this.privileges.toggleCredentialForAction(action, credential);
this.privilegesManager.savePrivileges(); this.application.privilegesManager.savePrivileges();
} }
cancel() { cancel() {

View File

@@ -7,23 +7,17 @@ class RevisionPreviewModalCtrl {
$element, $element,
$scope, $scope,
$timeout, $timeout,
alertManager,
componentManager,
modelManager,
syncManager, syncManager,
) { ) {
this.$element = $element; this.$element = $element;
this.$scope = $scope; this.$scope = $scope;
this.$timeout = $timeout; this.$timeout = $timeout;
this.alertManager = alertManager;
this.componentManager = componentManager;
this.modelManager = modelManager;
this.syncManager = syncManager; this.syncManager = syncManager;
this.createNote(); this.createNote();
this.configureEditor(); this.configureEditor();
$scope.$on('$destroy', () => { $scope.$on('$destroy', () => {
if (this.identifier) { if (this.identifier) {
this.componentManager.deregisterHandler(this.identifier); this.application.componentManager.deregisterHandler(this.identifier);
} }
}); });
} }
@@ -41,7 +35,7 @@ class RevisionPreviewModalCtrl {
* for note as not to save changes to original, if editor makes changes. * for note as not to save changes to original, if editor makes changes.
*/ */
this.note.uuid = this.uuid; this.note.uuid = this.uuid;
const editorForNote = this.componentManager.editorForNote(this.note); const editorForNote = this.application.componentManager.editorForNote(this.note);
this.note.uuid = protocolManager.crypto.generateUUIDSync(); this.note.uuid = protocolManager.crypto.generateUUIDSync();
if (editorForNote) { if (editorForNote) {
/** /**
@@ -55,7 +49,7 @@ class RevisionPreviewModalCtrl {
editorCopy.readonly = true; editorCopy.readonly = true;
editorCopy.lockReadonly = true; editorCopy.lockReadonly = true;
this.identifier = editorCopy.uuid; this.identifier = editorCopy.uuid;
this.componentManager.registerHandler({ this.application.componentManager.registerHandler({
identifier: this.identifier, identifier: this.identifier,
areas: ['editor-editor'], areas: ['editor-editor'],
contextRequestHandler: (component) => { contextRequestHandler: (component) => {
@@ -75,21 +69,21 @@ class RevisionPreviewModalCtrl {
} }
restore(asCopy) { restore(asCopy) {
const run = () => { const run = async () => {
let item; let item;
if (asCopy) { if (asCopy) {
const contentCopy = Object.assign({}, this.content); const contentCopy = Object.assign({}, this.content);
if (contentCopy.title) { if (contentCopy.title) {
contentCopy.title += " (copy)"; contentCopy.title += " (copy)";
} }
item = this.modelManager.createItem({ item = await this.application.createItem({
content_type: 'Note', contentType: 'Note',
content: contentCopy content: contentCopy,
needsSync: true
}); });
this.modelManager.addItem(item);
} else { } else {
const uuid = this.uuid; const uuid = this.uuid;
item = this.modelManager.findItem(uuid); item = this.application.findItem({uuid: uuid});
item.content = Object.assign({}, this.content); item.content = Object.assign({}, this.content);
this.modelManager.mapResponseItemsToLocalModels( this.modelManager.mapResponseItemsToLocalModels(
[item], [item],
@@ -102,7 +96,7 @@ class RevisionPreviewModalCtrl {
}; };
if (!asCopy) { if (!asCopy) {
this.alertManager.confirm({ this.application.alertManager.confirm({
text: "Are you sure you want to replace the current note's contents with what you see in this preview?", text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
destructive: true, destructive: true,
onConfirm: run onConfirm: run

View File

@@ -12,11 +12,6 @@ import '../../../vendor/assets/javascripts/zip/inflate';
import '../../../vendor/assets/javascripts/zip/zip'; import '../../../vendor/assets/javascripts/zip/zip';
import '../../../vendor/assets/javascripts/zip/z-worker'; import '../../../vendor/assets/javascripts/zip/z-worker';
import { SFItem } from 'snjs';
// Set the app domain before starting the app
SFItem.AppDomain = 'org.standardnotes.sn';
// entry point // entry point
// eslint-disable-next-line import/first // eslint-disable-next-line import/first
import './app'; import './app';

View File

@@ -1,7 +1,7 @@
import { SFAlertManager } from 'snjs'; import { SNAlertManager } from 'snjs';
import { SKAlert } from 'sn-stylekit'; import { SKAlert } from 'sn-stylekit';
export class AlertManager extends SFAlertManager { export class AlertManager extends SNAlertManager {
/* @ngInject */ /* @ngInject */
constructor($timeout) { constructor($timeout) {
super(); super();

View File

@@ -2,8 +2,8 @@ import { PrivilegesManager } from '@/services/privilegesManager';
export class ArchiveManager { export class ArchiveManager {
/* @ngInject */ /* @ngInject */
constructor(passcodeManager, authManager, modelManager, privilegesManager) { constructor(lockManager, authManager, modelManager, privilegesManager) {
this.passcodeManager = passcodeManager; this.lockManager = lockManager;
this.authManager = authManager; this.authManager = authManager;
this.modelManager = modelManager; this.modelManager = modelManager;
this.privilegesManager = privilegesManager; this.privilegesManager = privilegesManager;
@@ -22,9 +22,9 @@ export class ArchiveManager {
// download in Standard Notes format // download in Standard Notes format
let keys, authParams; let keys, authParams;
if(encrypted) { if(encrypted) {
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) { if(this.authManager.offline() && this.lockManager.hasPasscode()) {
keys = this.passcodeManager.keys(); keys = this.lockManager.keys();
authParams = this.passcodeManager.passcodeAuthParams(); authParams = this.lockManager.passcodeAuthParams();
} else { } else {
keys = await this.authManager.keys(); keys = await this.authManager.keys();
authParams = await this.authManager.getAuthParams(); authParams = await this.authManager.getAuthParams();
@@ -42,7 +42,7 @@ export class ArchiveManager {
}; };
if(await this.privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageBackups)) { if(await this.privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageBackups)) {
this.privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageBackups, () => { this.godService.presentPrivilegesModal(PrivilegesManager.ActionManageBackups, () => {
run(); run();
}); });
} else { } else {

View File

@@ -1,140 +0,0 @@
import angular from 'angular';
import { StorageManager } from './storageManager';
import { protocolManager, SFAuthManager } from 'snjs';
export class AuthManager extends SFAuthManager {
/* @ngInject */
constructor(
modelManager,
singletonManager,
storageManager,
dbManager,
httpManager,
$rootScope,
$timeout,
$compile
) {
super(storageManager, httpManager, null, $timeout);
this.$rootScope = $rootScope;
this.$compile = $compile;
this.modelManager = modelManager;
this.singletonManager = singletonManager;
this.storageManager = storageManager;
this.dbManager = dbManager;
}
loadInitialData() {
const userData = this.storageManager.getItemSync("user");
if(userData) {
this.user = JSON.parse(userData);
} else {
// legacy, check for uuid
const idData = this.storageManager.getItemSync("uuid");
if(idData) {
this.user = {uuid: idData};
}
}
this.checkForSecurityUpdate();
}
offline() {
return !this.user;
}
isEphemeralSession() {
if(this.ephemeral == null || this.ephemeral == undefined) {
this.ephemeral = JSON.parse(this.storageManager.getItemSync("ephemeral", StorageManager.Fixed));
}
return this.ephemeral;
}
setEphemeral(ephemeral) {
this.ephemeral = ephemeral;
if(ephemeral) {
this.storageManager.setModelStorageMode(StorageManager.Ephemeral);
this.storageManager.setItemsMode(StorageManager.Ephemeral);
} else {
this.storageManager.setModelStorageMode(StorageManager.Fixed);
this.storageManager.setItemsMode(this.storageManager.bestStorageMode());
this.storageManager.setItem("ephemeral", JSON.stringify(false), StorageManager.Fixed);
}
}
async getAuthParamsForEmail(url, email, extraParams) {
return super.getAuthParamsForEmail(url, email, extraParams);
}
async login(url, email, password, ephemeral, strictSignin, extraParams) {
return super.login(url, email, password, strictSignin, extraParams).then((response) => {
if(!response.error) {
this.setEphemeral(ephemeral);
this.checkForSecurityUpdate();
}
return response;
});
}
async register(url, email, password, ephemeral) {
return super.register(url, email, password).then((response) => {
if(!response.error) {
this.setEphemeral(ephemeral);
}
return response;
});
}
async changePassword(url, email, current_server_pw, newKeys, newAuthParams) {
return super.changePassword(url, email, current_server_pw, newKeys, newAuthParams).then((response) => {
if(!response.error) {
this.checkForSecurityUpdate();
}
return response;
});
}
async handleAuthResponse(response, email, url, authParams, keys) {
try {
await super.handleAuthResponse(response, email, url, authParams, keys);
this.user = response.user;
this.storageManager.setItem("user", JSON.stringify(response.user));
} catch (e) {
this.dbManager.displayOfflineAlert();
}
}
async verifyAccountPassword(password) {
const authParams = await this.getAuthParams();
const keys = await protocolManager.computeEncryptionKeysForUser(password, authParams);
const success = keys.mk === (await this.keys()).mk;
return success;
}
async checkForSecurityUpdate() {
if(this.offline()) {
return false;
}
const latest = protocolManager.version();
const updateAvailable = await this.protocolVersion() !== latest;
if(updateAvailable !== this.securityUpdateAvailable) {
this.securityUpdateAvailable = updateAvailable;
this.$rootScope.$broadcast("security-update-status-changed");
}
return this.securityUpdateAvailable;
}
presentPasswordWizard(type) {
var scope = this.$rootScope.$new(true);
scope.type = type;
var el = this.$compile( "<password-wizard type='type'></password-wizard>" )(scope);
angular.element(document.body).append(el);
}
signOut() {
super.signout();
this.user = null;
this._authParams = null;
}
}

View File

@@ -1,50 +0,0 @@
import angular from 'angular';
import { SNComponentManager, SFAlertManager } from 'snjs';
import { isDesktopApplication, getPlatformString } from '@/utils';
export class ComponentManager extends SNComponentManager {
/* @ngInject */
constructor(
modelManager,
syncManager,
desktopManager,
nativeExtManager,
$rootScope,
$timeout,
$compile
) {
super({
modelManager,
syncManager,
desktopManager,
nativeExtManager,
alertManager: new SFAlertManager(),
$uiRunner: $rootScope.safeApply,
$timeout: $timeout,
environment: isDesktopApplication() ? "desktop" : "web",
platform: getPlatformString()
});
// this.loggingEnabled = true;
this.$compile = $compile;
this.$rootScope = $rootScope;
}
openModalComponent(component) {
var scope = this.$rootScope.$new(true);
scope.component = component;
var el = this.$compile( "<component-modal component='component' class='sk-modal'></component-modal>" )(scope);
angular.element(document.body).append(el);
}
presentPermissionsDialog(dialog) {
const scope = this.$rootScope.$new(true);
scope.permissionsString = dialog.permissionsString;
scope.component = dialog.component;
scope.callback = dialog.callback;
var el = this.$compile( "<permissions-modal component='component' permissions-string='permissionsString' callback='callback' class='sk-modal'></permissions-modal>" )(scope);
angular.element(document.body).append(el);
}
}

View File

@@ -1,4 +1,4 @@
export class DBManager { export class DatabaseManager {
/* @ngInject */ /* @ngInject */
constructor(alertManager) { constructor(alertManager) {
this.locked = true; this.locked = true;

View File

@@ -15,10 +15,10 @@ export class DesktopManager {
modelManager, modelManager,
syncManager, syncManager,
authManager, authManager,
passcodeManager, lockManager,
appState appState
) { ) {
this.passcodeManager = passcodeManager; this.lockManager = lockManager;
this.modelManager = modelManager; this.modelManager = modelManager;
this.authManager = authManager; this.authManager = authManager;
this.syncManager = syncManager; this.syncManager = syncManager;
@@ -202,9 +202,9 @@ export class DesktopManager {
async desktop_requestBackupFile(callback) { async desktop_requestBackupFile(callback) {
let keys, authParams; let keys, authParams;
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) { if(this.authManager.offline() && this.lockManager.hasPasscode()) {
keys = this.passcodeManager.keys(); keys = this.lockManager.keys();
authParams = this.passcodeManager.passcodeAuthParams(); authParams = this.lockManager.passcodeAuthParams();
} else { } else {
keys = await this.authManager.keys(); keys = await this.authManager.keys();
authParams = await this.authManager.getAuthParams(); authParams = await this.authManager.getAuthParams();

View File

@@ -1,44 +1,36 @@
import angular from 'angular'; import angular from 'angular';
import { SFPrivilegesManager } from 'snjs';
export class PrivilegesManager extends SFPrivilegesManager { export class GodService {
/* @ngInject */ /* @ngInject */
constructor( constructor(
passcodeManager,
authManager,
syncManager,
singletonManager,
modelManager,
storageManager,
$rootScope, $rootScope,
$compile $compile
) { ) {
super(modelManager, syncManager, singletonManager);
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
this.$compile = $compile; this.$compile = $compile;
}
this.setDelegate({ async checkForSecurityUpdate() {
isOffline: async () => { if (this.offline()) {
return authManager.offline(); return false;
}, }
hasLocalPasscode: async () => {
return passcodeManager.hasPasscode(); const latest = protocolManager.version();
}, const updateAvailable = await this.protocolVersion() !== latest;
saveToStorage: async (key, value) => { if (updateAvailable !== this.securityUpdateAvailable) {
return storageManager.setItem(key, value, storageManager.bestStorageMode()); this.securityUpdateAvailable = updateAvailable;
}, this.$rootScope.$broadcast("security-update-status-changed");
getFromStorage: async (key) => { }
return storageManager.getItem(key, storageManager.bestStorageMode());
}, return this.securityUpdateAvailable;
verifyAccountPassword: async (password) => { }
return authManager.verifyAccountPassword(password);
}, presentPasswordWizard(type) {
verifyLocalPasscode: async (passcode) => { var scope = this.$rootScope.$new(true);
return passcodeManager.verifyPasscode(passcode); scope.type = type;
}, var el = this.$compile("<password-wizard type='type'></password-wizard>")(scope);
}); angular.element(document.body).append(el);
} }
async presentPrivilegesModal(action, onSuccess, onCancel) { async presentPrivilegesModal(action, onSuccess, onCancel) {

View File

@@ -1,13 +0,0 @@
import { SFHttpManager } from 'snjs';
export class HttpManager extends SFHttpManager {
/* @ngInject */
constructor(storageManager, $timeout) {
// calling callbacks in a $timeout allows UI to update
super($timeout);
this.setJWTRequestHandler(async () => {
return storageManager.getItem('jwt');
});
}
}

View File

@@ -2,14 +2,14 @@ export { ActionsManager } from './actionsManager';
export { ArchiveManager } from './archiveManager'; export { ArchiveManager } from './archiveManager';
export { AuthManager } from './authManager'; export { AuthManager } from './authManager';
export { ComponentManager } from './componentManager'; export { ComponentManager } from './componentManager';
export { DBManager } from './dbManager'; export { DatabaseManager } from './databaseManager';
export { DesktopManager } from './desktopManager'; export { DesktopManager } from './desktopManager';
export { HttpManager } from './httpManager'; export { HttpManager } from './httpManager';
export { KeyboardManager } from './keyboardManager'; export { KeyboardManager } from './keyboardManager';
export { MigrationManager } from './migrationManager'; export { MigrationManager } from './migrationManager';
export { ModelManager } from './modelManager'; export { ModelManager } from './modelManager';
export { NativeExtManager } from './nativeExtManager'; export { NativeExtManager } from './nativeExtManager';
export { PasscodeManager } from './passcodeManager'; export { LockManager } from './lockManager';
export { PrivilegesManager } from './privilegesManager'; export { PrivilegesManager } from './privilegesManager';
export { SessionHistory } from './sessionHistory'; export { SessionHistory } from './sessionHistory';
export { SingletonManager } from './singletonManager'; export { SingletonManager } from './singletonManager';

View File

@@ -0,0 +1,143 @@
import _ from 'lodash';
import { isDesktopApplication } from '@/utils';
import {
APP_STATE_EVENT_WINDOW_DID_BLUR,
APP_STATE_EVENT_WINDOW_DID_FOCUS
} from '../state';
const MILLISECONDS_PER_SECOND = 1000;
const FOCUS_POLL_INTERVAL = 1 * MILLISECONDS_PER_SECOND;
const LOCK_INTERVAL_NONE = 0;
const LOCK_INTERVAL_IMMEDIATE = 1;
const LOCK_INTERVAL_ONE_MINUTE = 60 * MILLISECONDS_PER_SECOND;
const LOCK_INTERVAL_FIVE_MINUTES = 300 * MILLISECONDS_PER_SECOND;
const LOCK_INTERVAL_ONE_HOUR= 3600 * MILLISECONDS_PER_SECOND;
const STORAGE_KEY_AUTOLOCK_INTERVAL = "AutoLockIntervalKey";
export class LockManager {
/* @ngInject */
constructor($rootScope, application, appState) {
this.$rootScope = $rootScope;
this.application = application;
this.appState = appState;
this.observeVisibility();
}
observeVisibility() {
this.appState.addObserver((eventName, data) => {
if(eventName === APP_STATE_EVENT_WINDOW_DID_BLUR) {
this.documentVisibilityChanged(false);
} else if(eventName === APP_STATE_EVENT_WINDOW_DID_FOCUS) {
this.documentVisibilityChanged(true);
}
});
if (!isDesktopApplication()) {
this.beginWebFocusPolling();
}
}
async setAutoLockInterval(interval) {
return this.application.setValue(
STORAGE_KEY_AUTOLOCK_INTERVAL,
JSON.stringify(interval),
);
}
async getAutoLockInterval() {
const interval = await this.application.getValue(
STORAGE_KEY_AUTOLOCK_INTERVAL,
);
if(interval) {
return JSON.parse(interval);
} else {
return LOCK_INTERVAL_NONE;
}
}
/**
* Verify document is in focus every so often as visibilitychange event is
* not triggered on a typical window blur event but rather on tab changes.
*/
beginWebFocusPolling() {
this.pollFocusTimeout = setInterval(() => {
const hasFocus = document.hasFocus();
if(hasFocus && this.lastFocusState === 'hidden') {
this.documentVisibilityChanged(true);
} else if(!hasFocus && this.lastFocusState === 'visible') {
this.documentVisibilityChanged(false);
}
/* Save this to compare against next time around */
this.lastFocusState = hasFocus ? 'visible' : 'hidden';
}, FOCUS_POLL_INTERVAL);
}
getAutoLockIntervalOptions() {
return [
{
value: LOCK_INTERVAL_NONE,
label: "Off"
},
{
value: LOCK_INTERVAL_IMMEDIATE,
label: "Immediately"
},
{
value: LOCK_INTERVAL_ONE_MINUTE,
label: "1m"
},
{
value: LOCK_INTERVAL_FIVE_MINUTES,
label: "5m"
},
{
value: LOCK_INTERVAL_ONE_HOUR,
label: "1h"
}
];
}
documentVisibilityChanged(visible) {
if(visible) {
if(
!this.isLocked() &&
this.lockAfterDate &&
new Date() > this.lockAfterDate
) {
this.application.passcodeLock();
}
this.cancelAutoLockTimer();
} else {
this.beginAutoLockTimer();
}
}
async beginAutoLockTimer() {
var interval = await this.getAutoLockInterval();
if(interval === LOCK_INTERVAL_NONE) {
return;
}
/**
* Use a timeout if possible, but if the computer is put to sleep, timeouts won't
* work. Need to set a date as backup. this.lockAfterDate does not need to be
* persisted, as living in memory is sufficient. If memory is cleared, then the
* application will lock anyway.
*/
const addToNow = (seconds) => {
const date = new Date();
date.setSeconds(date.getSeconds() + seconds);
return date;
};
this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND);
this.lockTimeout = setTimeout(() => {
this.cancelAutoLockTimer();
this.application.passcodeLock();
this.lockAfterDate = null;
}, interval);
}
cancelAutoLockTimer() {
clearTimeout(this.lockTimeout);
this.lockAfterDate = null;
}
}

View File

@@ -1,172 +0,0 @@
import { isDesktopApplication } from '@/utils';
import { SFMigrationManager } from 'snjs';
import { ComponentManager } from '@/services/componentManager';
export class MigrationManager extends SFMigrationManager {
/* @ngInject */
constructor(
modelManager,
syncManager,
componentManager,
storageManager,
statusManager,
authManager,
desktopManager
) {
super(modelManager, syncManager, storageManager, authManager);
this.componentManager = componentManager;
this.statusManager = statusManager;
this.desktopManager = desktopManager;
}
registeredMigrations() {
return [
this.editorToComponentMigration(),
this.componentUrlToHostedUrl(),
this.removeTagReferencesFromNotes()
];
}
/*
Migrate SN|Editor to SN|Component. Editors are deprecated as of November 2017. Editors using old APIs must
convert to using the new component API.
*/
editorToComponentMigration() {
return {
name: "editor-to-component",
content_type: "SN|Editor",
handler: async (editors) => {
// Convert editors to components
for(var editor of editors) {
// If there's already a component for this url, then skip this editor
if(editor.url && !this.componentManager.componentForUrl(editor.url)) {
var component = this.modelManager.createItem({
content_type: "SN|Component",
content: {
url: editor.url,
name: editor.name,
area: "editor-editor"
}
});
component.setAppDataItem("data", editor.data);
this.modelManager.addItem(component);
this.modelManager.setItemDirty(component, true);
}
}
for(const editor of editors) {
this.modelManager.setItemToBeDeleted(editor);
}
this.syncManager.sync();
}
};
}
/*
Migrate component.url fields to component.hosted_url. This involves rewriting any note data that relied on the
component.url value to store clientData, such as the CodeEditor, which stores the programming language for the note
in the note's clientData[component.url]. We want to rewrite any matching items to transfer that clientData into
clientData[component.uuid].
April 3, 2019 note: it seems this migration is mis-named. The first part of the description doesn't match what the code is actually doing.
It has nothing to do with url/hosted_url relationship and more to do with just mapping client data from the note's hosted_url to its uuid
Created: July 6, 2018
*/
componentUrlToHostedUrl() {
return {
name: "component-url-to-hosted-url",
content_type: "SN|Component",
handler: async (components) => {
let hasChanges = false;
const notes = this.modelManager.validItemsForContentType("Note");
for(const note of notes) {
for(const component of components) {
const clientData = note.getDomainDataItem(component.hosted_url, ComponentManager.ClientDataDomain);
if(clientData) {
note.setDomainDataItem(component.uuid, clientData, ComponentManager.ClientDataDomain);
note.setDomainDataItem(component.hosted_url, null, ComponentManager.ClientDataDomain);
this.modelManager.setItemDirty(note, true);
hasChanges = true;
}
}
}
if(hasChanges) {
this.syncManager.sync();
}
}
};
}
/*
Migrate notes which have relationships on tags to migrate those relationships to the tags themselves.
That is, notes.content.references should not include any mention of tags.
This will apply to notes created before the schema change. Now, only tags reference notes.
Created: April 3, 2019
*/
removeTagReferencesFromNotes() {
return {
name: "remove-tag-references-from-notes",
content_type: "Note",
handler: async (notes) => {
const needsSync = false;
let status = this.statusManager.addStatusFromString("Optimizing data...");
let dirtyCount = 0;
for(const note of notes) {
if(!note.content) {
continue;
}
const references = note.content.references;
// Remove any tag references, and transfer them to the tag if neccessary.
const newReferences = [];
for(const reference of references) {
if(reference.content_type != "Tag") {
newReferences.push(reference);
continue;
}
// is Tag content_type, we will not be adding this to newReferences
const tag = this.modelManager.findItem(reference.uuid);
if(tag && !tag.hasRelationshipWithItem(note)) {
tag.addItemAsRelationship(note);
this.modelManager.setItemDirty(tag, true);
dirtyCount++;
}
}
if(newReferences.length != references.length) {
note.content.references = newReferences;
this.modelManager.setItemDirty(note, true);
dirtyCount++;
}
}
if(dirtyCount > 0) {
if(isDesktopApplication()) {
this.desktopManager.saveBackup();
}
status = this.statusManager.replaceStatusWithString(status, `${dirtyCount} items optimized.`);
await this.syncManager.sync();
status = this.statusManager.replaceStatusWithString(status, `Optimization complete.`);
setTimeout(() => {
this.statusManager.removeStatus(status);
}, 2000);
} else {
this.statusManager.removeStatus(status);
}
}
};
}
}

View File

@@ -1,170 +0,0 @@
import _ from 'lodash';
import { SFModelManager, SNSmartTag, SFPredicate } from 'snjs';
export class ModelManager extends SFModelManager {
/* @ngInject */
constructor(storageManager, $timeout) {
super($timeout);
this.notes = [];
this.tags = [];
this.components = [];
this.storageManager = storageManager;
this.buildSystemSmartTags();
}
handleSignout() {
super.handleSignout();
this.notes.length = 0;
this.tags.length = 0;
this.components.length = 0;
}
noteCount() {
return this.notes.filter((n) => !n.dummy).length;
}
removeAllItemsFromMemory() {
for(var item of this.items) {
item.deleted = true;
}
this.notifySyncObserversOfModels(this.items);
this.handleSignout();
}
findTag(title) {
return _.find(this.tags, { title: title });
}
findOrCreateTagByTitle(title) {
let tag = this.findTag(title);
if(!tag) {
tag = this.createItem({content_type: "Tag", content: {title: title}});
this.addItem(tag);
this.setItemDirty(tag, true);
}
return tag;
}
addItems(items, globalOnly = false) {
super.addItems(items, globalOnly);
items.forEach((item) => {
// In some cases, you just want to add the item to this.items, and not to the individual arrays
// This applies when you want to keep an item syncable, but not display it via the individual arrays
if(!globalOnly) {
if(item.content_type == "Tag") {
if(!_.find(this.tags, {uuid: item.uuid})) {
this.tags.splice(_.sortedIndexBy(this.tags, item, function(item){
if (item.title) return item.title.toLowerCase();
else return '';
}), 0, item);
}
} else if(item.content_type == "Note") {
if(!_.find(this.notes, {uuid: item.uuid})) {
this.notes.unshift(item);
}
} else if(item.content_type == "SN|Component") {
if(!_.find(this.components, {uuid: item.uuid})) {
this.components.unshift(item);
}
}
}
});
}
resortTag(tag) {
_.pull(this.tags, tag);
this.tags.splice(_.sortedIndexBy(this.tags, tag, function(tag){
if (tag.title) return tag.title.toLowerCase();
else return '';
}), 0, tag);
}
setItemToBeDeleted(item) {
super.setItemToBeDeleted(item);
// remove from relevant array, but don't remove from all items.
// This way, it's removed from the display, but still synced via get dirty items
this.removeItemFromRespectiveArray(item);
}
removeItemLocally(item, callback) {
super.removeItemLocally(item, callback);
this.removeItemFromRespectiveArray(item);
this.storageManager.deleteModel(item).then(callback);
}
removeItemFromRespectiveArray(item) {
if(item.content_type == "Tag") {
_.remove(this.tags, {uuid: item.uuid});
} else if(item.content_type == "Note") {
_.remove(this.notes, {uuid: item.uuid});
} else if(item.content_type == "SN|Component") {
_.remove(this.components, {uuid: item.uuid});
}
}
notesMatchingSmartTag(tag) {
const contentTypePredicate = new SFPredicate("content_type", "=", "Note");
const predicates = [contentTypePredicate, tag.content.predicate];
if(!tag.content.isTrashTag) {
const notTrashedPredicate = new SFPredicate("content.trashed", "=", false);
predicates.push(notTrashedPredicate);
}
const results = this.itemsMatchingPredicates(predicates);
return results;
}
trashSmartTag() {
return this.systemSmartTags.find((tag) => tag.content.isTrashTag);
}
trashedItems() {
return this.notesMatchingSmartTag(this.trashSmartTag());
}
emptyTrash() {
const notes = this.trashedItems();
for(const note of notes) {
this.setItemToBeDeleted(note);
}
}
buildSystemSmartTags() {
this.systemSmartTags = SNSmartTag.systemSmartTags();
}
getSmartTagWithId(id) {
return this.getSmartTags().find((candidate) => candidate.uuid == id);
}
getSmartTags() {
const userTags = this.validItemsForContentType("SN|SmartTag").sort((a, b) => {
return a.content.title < b.content.title ? -1 : 1;
});
return this.systemSmartTags.concat(userTags);
}
/*
Misc
*/
humanReadableDisplayForContentType(contentType) {
return {
"Note" : "note",
"Tag" : "tag",
"SN|SmartTag": "smart tag",
"Extension" : "action-based extension",
"SN|Component" : "component",
"SN|Editor" : "editor",
"SN|Theme" : "theme",
"SF|Extension" : "server extension",
"SF|MFA" : "two-factor authentication setting",
"SN|FileSafe|Credentials": "FileSafe credential",
"SN|FileSafe|FileMetadata": "FileSafe file",
"SN|FileSafe|Integration": "FileSafe integration"
}[contentType];
}
}

View File

@@ -1,285 +0,0 @@
import _ from 'lodash';
import { isDesktopApplication } from '@/utils';
import { StorageManager } from './storageManager';
import { protocolManager } from 'snjs';
const MillisecondsPerSecond = 1000;
export class PasscodeManager {
/* @ngInject */
constructor($rootScope, authManager, storageManager, syncManager) {
this.authManager = authManager;
this.storageManager = storageManager;
this.syncManager = syncManager;
this.$rootScope = $rootScope;
this._hasPasscode = this.storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
this._locked = this._hasPasscode;
this.visibilityObservers = [];
this.passcodeChangeObservers = [];
this.configureAutoLock();
}
addPasscodeChangeObserver(callback) {
this.passcodeChangeObservers.push(callback);
}
lockApplication() {
window.location.reload();
this.cancelAutoLockTimer();
}
isLocked() {
return this._locked;
}
hasPasscode() {
return this._hasPasscode;
}
keys() {
return this._keys;
}
addVisibilityObserver(callback) {
this.visibilityObservers.push(callback);
return callback;
}
removeVisibilityObserver(callback) {
_.pull(this.visibilityObservers, callback);
}
notifiyVisibilityObservers(visible) {
for(const callback of this.visibilityObservers) {
callback(visible);
}
}
async setAutoLockInterval(interval) {
return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.FixedEncrypted);
}
async getAutoLockInterval() {
const interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.FixedEncrypted);
if(interval) {
return JSON.parse(interval);
} else {
return PasscodeManager.AutoLockIntervalNone;
}
}
passcodeAuthParams() {
var authParams = JSON.parse(this.storageManager.getItemSync("offlineParams", StorageManager.Fixed));
if(authParams && !authParams.version) {
var keys = this.keys();
if(keys && keys.ak) {
// If there's no version stored, and there's an ak, it has to be 002. Newer versions would have their version stored in authParams.
authParams.version = "002";
} else {
authParams.version = "001";
}
}
return authParams;
}
async verifyPasscode(passcode) {
return new Promise(async (resolve, reject) => {
var params = this.passcodeAuthParams();
const keys = await protocolManager.computeEncryptionKeysForUser(passcode, params);
if(keys.pw !== params.hash) {
resolve(false);
} else {
resolve(true);
}
});
}
unlock(passcode, callback) {
var params = this.passcodeAuthParams();
protocolManager.computeEncryptionKeysForUser(passcode, params).then((keys) => {
if(keys.pw !== params.hash) {
callback(false);
return;
}
this._keys = keys;
this._authParams = params;
this.decryptLocalStorage(keys, params).then(() => {
this._locked = false;
callback(true);
});
});
}
setPasscode(passcode, callback) {
var uuid = protocolManager.crypto.generateUUIDSync();
protocolManager.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
const keys = results.keys;
const authParams = results.authParams;
authParams.hash = keys.pw;
this._keys = keys;
this._hasPasscode = true;
this._authParams = authParams;
// Encrypting will initially clear localStorage
this.encryptLocalStorage(keys, authParams);
// After it's cleared, it's safe to write to it
this.storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
callback(true);
this.notifyObserversOfPasscodeChange();
});
}
changePasscode(newPasscode, callback) {
this.setPasscode(newPasscode, callback);
}
clearPasscode() {
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
this.storageManager.removeItem("offlineParams", StorageManager.Fixed);
this._keys = null;
this._hasPasscode = false;
this.notifyObserversOfPasscodeChange();
}
notifyObserversOfPasscodeChange() {
for(var observer of this.passcodeChangeObservers) {
observer();
}
}
encryptLocalStorage(keys, authParams) {
this.storageManager.setKeys(keys, authParams);
// Switch to Ephemeral storage, wiping Fixed storage
// Last argument is `force`, which we set to true because in the case of changing passcode
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
}
async decryptLocalStorage(keys, authParams) {
this.storageManager.setKeys(keys, authParams);
return this.storageManager.decryptStorage();
}
configureAutoLock() {
PasscodeManager.AutoLockPollFocusInterval = 1 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalNone = 0;
PasscodeManager.AutoLockIntervalImmediate = 1;
PasscodeManager.AutoLockIntervalOneMinute = 60 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalFiveMinutes = 300 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalOneHour = 3600 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalKey = "AutoLockIntervalKey";
if(isDesktopApplication()) {
// desktop only
this.$rootScope.$on("window-lost-focus", () => {
this.documentVisibilityChanged(false);
});
this.$rootScope.$on("window-gained-focus", () => {
this.documentVisibilityChanged(true);
});
} else {
// tab visibility listener, web only
document.addEventListener('visibilitychange', (e) => {
const visible = document.visibilityState === "visible";
this.documentVisibilityChanged(visible);
});
// verify document is in focus every so often as visibilitychange event is not triggered
// on a typical window blur event but rather on tab changes
this.pollFocusTimeout = setInterval(() => {
const hasFocus = document.hasFocus();
if(hasFocus && this.lastFocusState === "hidden") {
this.documentVisibilityChanged(true);
} else if(!hasFocus && this.lastFocusState === "visible") {
this.documentVisibilityChanged(false);
}
// save this to compare against next time around
this.lastFocusState = hasFocus ? "visible" : "hidden";
}, PasscodeManager.AutoLockPollFocusInterval);
}
}
getAutoLockIntervalOptions() {
return [
{
value: PasscodeManager.AutoLockIntervalNone,
label: "Off"
},
{
value: PasscodeManager.AutoLockIntervalImmediate,
label: "Immediately"
},
{
value: PasscodeManager.AutoLockIntervalOneMinute,
label: "1m"
},
{
value: PasscodeManager.AutoLockIntervalFiveMinutes,
label: "5m"
},
{
value: PasscodeManager.AutoLockIntervalOneHour,
label: "1h"
}
];
}
documentVisibilityChanged(visible) {
if(visible) {
// check to see if lockAfterDate is not null, and if the application isn't locked.
// if that's the case, it needs to be locked immediately.
if(this.lockAfterDate && new Date() > this.lockAfterDate && !this.isLocked()) {
this.lockApplication();
} else {
if(!this.isLocked()) {
this.syncManager.sync();
}
}
this.cancelAutoLockTimer();
} else {
this.beginAutoLockTimer();
}
this.notifiyVisibilityObservers(visible);
}
async beginAutoLockTimer() {
var interval = await this.getAutoLockInterval();
if(interval == PasscodeManager.AutoLockIntervalNone) {
return;
}
// Use a timeout if possible, but if the computer is put to sleep, timeouts won't work.
// Need to set a date as backup. this.lockAfterDate does not need to be persisted, as
// living in memory seems sufficient. If memory is cleared, then the application will lock anyway.
const addToNow = (seconds) => {
const date = new Date();
date.setSeconds(date.getSeconds() + seconds);
return date;
};
this.lockAfterDate = addToNow(interval / MillisecondsPerSecond);
this.lockTimeout = setTimeout(() => {
this.lockApplication();
// We don't need to look at this anymore since we've succeeded with timeout lock
this.lockAfterDate = null;
}, interval);
}
cancelAutoLockTimer() {
clearTimeout(this.lockTimeout);
this.lockAfterDate = null;
}
}

View File

@@ -1,44 +0,0 @@
import { NoteHistoryEntry } from '@/models/noteHistoryEntry';
import { SFSessionHistoryManager , SFItemHistory } from 'snjs';
export class SessionHistory extends SFSessionHistoryManager {
/* @ngInject */
constructor(
modelManager,
storageManager,
authManager,
passcodeManager,
$timeout
) {
SFItemHistory.HistoryEntryClassMapping = {
"Note" : NoteHistoryEntry
};
// Session History can be encrypted with passcode keys. If it changes, we need to resave session
// history with the new keys.
passcodeManager.addPasscodeChangeObserver(() => {
this.saveToDisk();
});
var keyRequestHandler = async () => {
const offline = authManager.offline();
const auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams();
const keys = offline ? passcodeManager.keys() : await authManager.keys();
return {
keys: keys,
offline: offline,
auth_params: auth_params
};
};
var contentTypes = ["Note"];
super(
modelManager,
storageManager,
keyRequestHandler,
contentTypes,
$timeout
);
}
}

View File

@@ -1,10 +0,0 @@
import { SFSingletonManager } from 'snjs';
export class SingletonManager extends SFSingletonManager {
// constructor needed for angularjs injection to work
// eslint-disable-next-line no-useless-constructor
/* @ngInject */
constructor(modelManager, syncManager) {
super(modelManager, syncManager);
}
}

View File

@@ -1,241 +0,0 @@
import { protocolManager, SNEncryptedStorage, SFStorageManager , SFItemParams } from 'snjs';
export class MemoryStorage {
constructor() {
this.memory = {};
}
getItem(key) {
return this.memory[key] || null;
}
getItemSync(key) {
return this.getItem(key);
}
get length() {
return Object.keys(this.memory).length;
}
setItem(key, value) {
this.memory[key] = value;
}
removeItem(key) {
delete this.memory[key];
}
clear() {
this.memory = {};
}
keys() {
return Object.keys(this.memory);
}
key(index) {
return Object.keys(this.memory)[index];
}
}
export class StorageManager extends SFStorageManager {
/* @ngInject */
constructor(dbManager, alertManager) {
super();
this.dbManager = dbManager;
this.alertManager = alertManager;
}
initialize(hasPasscode, ephemeral) {
if(hasPasscode) {
// We don't want to save anything in fixed storage except for actual item data (in IndexedDB)
this.storage = this.memoryStorage;
this.itemsStorageMode = StorageManager.FixedEncrypted;
} else if(ephemeral) {
// We don't want to save anything in fixed storage as well as IndexedDB
this.storage = this.memoryStorage;
this.itemsStorageMode = StorageManager.Ephemeral;
} else {
this.storage = localStorage;
this.itemsStorageMode = StorageManager.Fixed;
}
this.modelStorageMode = ephemeral ? StorageManager.Ephemeral : StorageManager.Fixed;
}
get memoryStorage() {
if(!this._memoryStorage) {
this._memoryStorage = new MemoryStorage();
}
return this._memoryStorage;
}
setItemsMode(mode, force) {
var newStorage = this.getVault(mode);
if(newStorage !== this.storage || mode !== this.itemsStorageMode || force) {
// transfer storages
var length = this.storage.length;
for(var i = 0; i < length; i++) {
var key = this.storage.key(i);
newStorage.setItem(key, this.storage.getItem(key));
}
this.itemsStorageMode = mode;
if(newStorage !== this.storage) {
// Only clear if this.storage isn't the same reference as newStorage
this.storage.clear();
}
this.storage = newStorage;
if(mode == StorageManager.FixedEncrypted) {
this.writeEncryptedStorageToDisk();
} else if(mode == StorageManager.Fixed) {
// Remove encrypted storage
this.removeItem("encryptedStorage", StorageManager.Fixed);
}
}
}
getVault(vaultKey) {
if(vaultKey) {
if(vaultKey == StorageManager.Ephemeral || vaultKey == StorageManager.FixedEncrypted) {
return this.memoryStorage;
} else {
return localStorage;
}
} else {
return this.storage;
}
}
async setItem(key, value, vaultKey) {
var storage = this.getVault(vaultKey);
try {
storage.setItem(key, value);
} catch (e) {
console.error("Exception while trying to setItem in StorageManager:", e);
this.alertManager.alert({text: "The application's local storage is out of space. If you have Session History save-to-disk enabled, please disable it, and try again."});
}
if(vaultKey === StorageManager.FixedEncrypted || (!vaultKey && this.itemsStorageMode === StorageManager.FixedEncrypted)) {
return this.writeEncryptedStorageToDisk();
}
}
async getItem(key, vault) {
return this.getItemSync(key, vault);
}
getItemSync(key, vault) {
var storage = this.getVault(vault);
return storage.getItem(key);
}
async removeItem(key, vault) {
var storage = this.getVault(vault);
return storage.removeItem(key);
}
async clear() {
this.memoryStorage.clear();
localStorage.clear();
}
storageAsHash() {
var hash = {};
var length = this.storage.length;
for(var i = 0; i < length; i++) {
var key = this.storage.key(i);
hash[key] = this.storage.getItem(key);
}
return hash;
}
setKeys(keys, authParams) {
this.encryptedStorageKeys = keys;
this.encryptedStorageAuthParams = authParams;
}
async writeEncryptedStorageToDisk() {
var encryptedStorage = new SNEncryptedStorage();
// Copy over totality of current storage
encryptedStorage.content.storage = this.storageAsHash();
// Save new encrypted storage in Fixed storage
var params = new SFItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams);
const syncParams = await params.paramsForSync();
this.setItem("encryptedStorage", JSON.stringify(syncParams), StorageManager.Fixed);
}
async decryptStorage() {
var stored = JSON.parse(this.getItemSync("encryptedStorage", StorageManager.Fixed));
await protocolManager.decryptItem(stored, this.encryptedStorageKeys);
var encryptedStorage = new SNEncryptedStorage(stored);
for(var key of Object.keys(encryptedStorage.content.storage)) {
this.setItem(key, encryptedStorage.storage[key]);
}
}
hasPasscode() {
return this.getItemSync("encryptedStorage", StorageManager.Fixed) !== null;
}
bestStorageMode() {
return this.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Fixed;
}
/*
Model Storage
If using ephemeral storage, we don't need to write it to anything as references will be held already by controllers
and the global modelManager service.
*/
setModelStorageMode(mode) {
if(mode == this.modelStorageMode) {
return;
}
if(mode == StorageManager.Ephemeral) {
// Clear IndexedDB
this.dbManager.clearAllModels(null);
} else {
// Fixed
}
this.modelStorageMode = mode;
}
async getAllModels() {
if(this.modelStorageMode == StorageManager.Fixed) {
return this.dbManager.getAllModels();
}
}
async saveModel(item) {
return this.saveModels([item]);
}
async saveModels(items, onsuccess, onerror) {
if(this.modelStorageMode == StorageManager.Fixed) {
return this.dbManager.saveModels(items);
}
}
async deleteModel(item) {
if(this.modelStorageMode == StorageManager.Fixed) {
return this.dbManager.deleteModel(item);
}
}
async clearAllModels() {
return this.dbManager.clearAllModels();
}
}
StorageManager.FixedEncrypted = "FixedEncrypted"; // encrypted memoryStorage + localStorage persistence
StorageManager.Ephemeral = "Ephemeral"; // memoryStorage
StorageManager.Fixed = "Fixed"; // localStorage

View File

@@ -1,30 +0,0 @@
import angular from 'angular';
import { SFSyncManager } from 'snjs';
export class SyncManager extends SFSyncManager {
/* @ngInject */
constructor(
modelManager,
storageManager,
httpManager,
$timeout,
$interval,
$compile,
$rootScope
) {
super(modelManager, storageManager, httpManager, $timeout, $interval);
this.$rootScope = $rootScope;
this.$compile = $compile;
// this.loggingEnabled = true;
}
presentConflictResolutionModal(items, callback) {
var scope = this.$rootScope.$new(true);
scope.item1 = items[0];
scope.item2 = items[1];
scope.callback = callback;
var el = this.$compile( "<conflict-resolution-modal item1='item1' item2='item2' callback='callback' class='sk-modal'></conflict-resolution-modal>" )(scope);
angular.element(document.body).append(el);
}
}

View File

@@ -12,7 +12,7 @@ export class ThemeManager {
componentManager, componentManager,
desktopManager, desktopManager,
storageManager, storageManager,
passcodeManager, lockManager,
appState appState
) { ) {
this.componentManager = componentManager; this.componentManager = componentManager;
@@ -24,13 +24,6 @@ export class ThemeManager {
this.registerObservers(); this.registerObservers();
// When a passcode is added, all local storage will be encrypted (it doesn't know what was
// originally saved as Fixed or FixedEncrypted). We want to rewrite cached themes here to Fixed
// so that it's readable without authentication.
passcodeManager.addPasscodeChangeObserver(() => {
this.cacheThemes();
});
if (desktopManager.isDesktop) { if (desktopManager.isDesktop) {
appState.addObserver((eventName, data) => { appState.addObserver((eventName, data) => {
if (eventName === APP_STATE_EVENT_DESKTOP_EXTS_READY) { if (eventName === APP_STATE_EVENT_DESKTOP_EXTS_READY) {

View File

@@ -1,4 +1,6 @@
import { PrivilegesManager } from '@/services/privilegesManager'; import { PrivilegesManager } from '@/services/privilegesManager';
import { isDesktopApplication } from '@/utils';
import pull from 'lodash/pull';
export const APP_STATE_EVENT_TAG_CHANGED = 1; export const APP_STATE_EVENT_TAG_CHANGED = 1;
export const APP_STATE_EVENT_NOTE_CHANGED = 2; export const APP_STATE_EVENT_NOTE_CHANGED = 2;
@@ -8,6 +10,8 @@ export const APP_STATE_EVENT_EDITOR_FOCUSED = 5;
export const APP_STATE_EVENT_BEGAN_BACKUP_DOWNLOAD = 6; export const APP_STATE_EVENT_BEGAN_BACKUP_DOWNLOAD = 6;
export const APP_STATE_EVENT_ENDED_BACKUP_DOWNLOAD = 7; export const APP_STATE_EVENT_ENDED_BACKUP_DOWNLOAD = 7;
export const APP_STATE_EVENT_DESKTOP_EXTS_READY = 8; export const APP_STATE_EVENT_DESKTOP_EXTS_READY = 8;
export const APP_STATE_EVENT_WINDOW_DID_FOCUS = 9;
export const APP_STATE_EVENT_WINDOW_DID_BLUR = 10;
export const EVENT_SOURCE_USER_INTERACTION = 1; export const EVENT_SOURCE_USER_INTERACTION = 1;
export const EVENT_SOURCE_SCRIPT = 2; export const EVENT_SOURCE_SCRIPT = 2;
@@ -15,15 +19,44 @@ export const EVENT_SOURCE_SCRIPT = 2;
export class AppState { export class AppState {
/* @ngInject */ /* @ngInject */
constructor($timeout, privilegesManager) { constructor(
$timeout,
$rootScope,
privilegesManager
) {
this.$timeout = $timeout; this.$timeout = $timeout;
this.$rootScope = $rootScope;
this.privilegesManager = privilegesManager; this.privilegesManager = privilegesManager;
this.observers = []; this.observers = [];
this.registerVisibilityObservers();
} }
registerVisibilityObservers() {
if (isDesktopApplication()) {
this.$rootScope.$on('window-lost-focus', () => {
this.notifyEvent(APP_STATE_EVENT_WINDOW_DID_BLUR);
});
this.$rootScope.$on('window-gained-focus', () => {
this.notifyEvent(APP_STATE_EVENT_WINDOW_DID_FOCUS);
});
} else {
/* Tab visibility listener, web only */
document.addEventListener('visibilitychange', (e) => {
const visible = document.visibilityState === "visible";
const event = visible
? APP_STATE_EVENT_WINDOW_DID_FOCUS
: APP_STATE_EVENT_WINDOW_DID_BLUR;
this.notifyEvent(event);
});
}
}
/** @returns A function that unregisters this observer */
addObserver(callback) { addObserver(callback) {
this.observers.push(callback); this.observers.push(callback);
return callback; return () => {
pull(this.observers, callback);
};
} }
async notifyEvent(eventName, data) { async notifyEvent(eventName, data) {
@@ -66,7 +99,7 @@ export class AppState {
await this.privilegesManager.actionRequiresPrivilege( await this.privilegesManager.actionRequiresPrivilege(
PrivilegesManager.ActionViewProtectedNotes PrivilegesManager.ActionViewProtectedNotes
)) { )) {
this.privilegesManager.presentPrivilegesModal( this.godService.presentPrivilegesModal(
PrivilegesManager.ActionViewProtectedNotes, PrivilegesManager.ActionViewProtectedNotes,
run run
); );

View File

@@ -22,8 +22,8 @@ export function isNullOrUndefined(value) {
export function getPlatformString() { export function getPlatformString() {
try { try {
var platform = navigator.platform.toLowerCase(); const platform = navigator.platform.toLowerCase();
var trimmed = ''; let trimmed = '';
if (platform.indexOf('mac') !== -1) { if (platform.indexOf('mac') !== -1) {
trimmed = 'mac'; trimmed = 'mac';
} else if (platform.indexOf('win') !== -1) { } else if (platform.indexOf('win') !== -1) {

View File

@@ -0,0 +1,114 @@
import { DeviceInterface } from 'snjs';
export class WebDeviceInterface extends DeviceInterface {
async getRawStorageValue(key) {
return localStorage.getItem(key);
}
async getAllRawStorageKeyValues() {
const results = [];
for(const key of Object.keys(localStorage)) {
results.push({
key: key,
value: localStorage[key]
});
}
return results;
}
async setRawStorageValue(key, value) {
localStorage.setItem(key, value);
}
async removeRawStorageValue(key) {
localStorage.removeItem(key);
}
async removeAllRawStorageValues() {
localStorage.clear();
}
/** @database */
_getDatabaseKeyPrefix() {
if(this.namespace) {
return `${this.namespace}-item-`;
} else {
return `item-`;
}
}
_keyForPayloadId(id) {
return `${this._getDatabaseKeyPrefix()}${id}`;
}
async getRawDatabasePayloadWithId(id) {
return localStorage.getItem(this._keyForPayloadId(id))
}
async getAllRawDatabasePayloads() {
const models = [];
for(const key in localStorage) {
if(key.startsWith(this._getDatabaseKeyPrefix())) {
models.push(JSON.parse(localStorage[key]))
}
}
return models;
}
async saveRawDatabasePayload(payload) {
localStorage.setItem(
this._keyForPayloadId(payload.uuid),
JSON.stringify(payload)
);
}
async saveRawDatabasePayloads(payloads) {
for(const payload of payloads) {
await this.saveRawDatabasePayload(payload);
}
}
async removeRawDatabasePayloadWithId(id) {
localStorage.removeItem(this._keyForPayloadId(id));
}
async removeAllRawDatabasePayloads() {
for(const key in localStorage) {
if(key.startsWith(this._getDatabaseKeyPrefix())) {
delete localStorage[key];
}
}
}
/** @keychian */
async getRawKeychainValue() {
if(this.keychainValue) {
return this.keychainValue;
} else {
const authParams = localStorage.getItem('auth_params');
if(!authParams) {
return null;
}
const version = JSON.parse(authParams).version;
return {
mk: localStorage.getItem('mk'),
pw: localStorage.getItem('pw'),
ak: localStorage.getItem('ak'),
version: version
}
}
}
async setKeychainValue(value) {
this.keychainValue = value;
}
async clearKeychainValue() {
this.keychainValue = null;
}
}

View File

@@ -204,10 +204,10 @@
.inline Security Update Available .inline Security Update Available
.sk-panel-section .sk-panel-section
.sk-panel-section-title Encryption .sk-panel-section-title Encryption
.sk-panel-section-subtitle.info(ng-if='self.encryptionEnabled()') .sk-panel-section-subtitle.info(ng-if='self.state.encryptionEnabled')
| {{self.encryptionStatusForNotes()}} | {{self.encryptionStatusForNotes()}}
p.sk-p p.sk-p
| {{self.encryptionStatusString()}} | {{self.state.encryptionStatusString}}
.sk-panel-section .sk-panel-section
.sk-panel-section-title Passcode Lock .sk-panel-section-title Passcode Lock
div(ng-if='!self.hasPasscode()') div(ng-if='!self.hasPasscode()')

View File

@@ -1,18 +1,17 @@
.main-ui-view( .main-ui-view(
ng-class='platform' ng-class='self.platformString'
) )
lock-screen( lock-screen(
ng-if='needsUnlock', ng-if='self.state.needsUnlock'
on-success='onSuccessfulUnlock'
) )
#app.app( #app.app(
ng-class='appClass', ng-class='self.state.appClass',
ng-if='!needsUnlock' ng-if='!self.state.needsUnlock'
) )
tags-panel tags-panel
notes-panel notes-panel
editor-panel editor-panel
footer( footer(
ng-if='!needsUnlock' ng-if='!self.state.needsUnlock'
) )

View File

@@ -29,7 +29,7 @@
<meta name="og:description" content="Standard Notes is a basic notes app that delivers only the essentials in note taking. Because of its simplicity and resistance to growth, users can count on durability and convenience."/> <meta name="og:description" content="Standard Notes is a basic notes app that delivers only the essentials in note taking. Because of its simplicity and resistance to growth, users can count on durability and convenience."/>
<script> <script>
window._default_sf_server = "<%= ENV['SF_DEFAULT_SERVER'] %>"; window._default_sync_server = "<%= ENV['SF_DEFAULT_SERVER'] %>";
window._extensions_manager_location = "<%= ENV['EXTENSIONS_MANAGER_LOCATION'] %>"; window._extensions_manager_location = "<%= ENV['EXTENSIONS_MANAGER_LOCATION'] %>";
window._batch_manager_location = "<%= ENV['BATCH_MANAGER_LOCATION'] %>"; window._batch_manager_location = "<%= ENV['BATCH_MANAGER_LOCATION'] %>";
</script> </script>

80773
dist/javascripts/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2652
dist/stylesheets/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -25,7 +25,7 @@
<title>Notes · Standard Notes</title> <title>Notes · Standard Notes</title>
<script> <script>
window._default_sf_server = "https://sync.standardnotes.org"; window._default_sync_server = "https://sync.standardnotes.org";
window._extensions_manager_location = "public/extensions/extensions-manager/dist/index.html"; window._extensions_manager_location = "public/extensions/extensions-manager/dist/index.html";
window._batch_manager_location = "public/extensions/batch-manager/dist/index.min.html"; window._batch_manager_location = "public/extensions/batch-manager/dist/index.min.html";
</script> </script>

14998
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,7 +47,7 @@
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
"sn-stylekit": "2.0.20", "sn-stylekit": "2.0.20",
"snjs": "1.0.6", "snjs": "file:~/Desktop/sn/dev/snjs",
"webpack": "^4.41.5", "webpack": "^4.41.5",
"webpack-cli": "^3.3.10" "webpack-cli": "^3.3.10"
}, },