From ac1b06967f5c67f3a522ea2f636dc8d9759e6b6e Mon Sep 17 00:00:00 2001 From: azivner Date: Sun, 25 Mar 2018 21:16:57 -0400 Subject: [PATCH] decoupled protected session holder from presentation stuff and similar things --- .../javascripts/dialogs/recent_notes.js | 9 +++ src/public/javascripts/dialogs/settings.js | 6 +- src/public/javascripts/services/bundle.js | 13 +++++ src/public/javascripts/services/export.js | 4 +- src/public/javascripts/services/init.js | 3 +- src/public/javascripts/services/messaging.js | 39 ++++--------- .../javascripts/services/note_detail.js | 21 +++++-- .../javascripts/services/protected_session.js | 49 +---------------- .../services/protected_session_holder.js | 55 +++++++++++++++++++ src/public/javascripts/services/server.js | 4 +- src/public/javascripts/services/tree.js | 14 ++++- src/public/javascripts/services/utils.js | 12 +--- 12 files changed, 130 insertions(+), 99 deletions(-) create mode 100644 src/public/javascripts/services/bundle.js create mode 100644 src/public/javascripts/services/protected_session_holder.js diff --git a/src/public/javascripts/dialogs/recent_notes.js b/src/public/javascripts/dialogs/recent_notes.js index 6c83b093a..23464c1e0 100644 --- a/src/public/javascripts/dialogs/recent_notes.js +++ b/src/public/javascripts/dialogs/recent_notes.js @@ -1,6 +1,7 @@ import treeService from '../services/tree.js'; import messagingService from '../services/messaging.js'; import server from '../services/server.js'; +import utils from "../services/utils"; const $dialog = $("#recent-notes-dialog"); const $searchInput = $('#recent-notes-search-input'); @@ -92,6 +93,14 @@ async function showDialog() { setTimeout(reload, 100); +messagingService.subscribeToMessages(syncData => { + if (syncData.some(sync => sync.entityName === 'recent_notes')) { + console.log(utils.now(), "Reloading recent notes because of background changes"); + + reload(); + } +}); + export default { showDialog, addRecentNote, diff --git a/src/public/javascripts/dialogs/settings.js b/src/public/javascripts/dialogs/settings.js index 022b02bec..d7a24b766 100644 --- a/src/public/javascripts/dialogs/settings.js +++ b/src/public/javascripts/dialogs/settings.js @@ -1,6 +1,6 @@ "use strict"; -import protectedSessionService from '../services/protected_session.js'; +import protectedSessionHolder from '../services/protected_session_holder.js'; import utils from '../services/utils.js'; import server from '../services/server.js'; @@ -77,7 +77,7 @@ addModule((function() { alert("Password has been changed. Trilium will be reloaded after you press OK."); // password changed so current protected session is invalid and needs to be cleared - protectedSessionService.resetProtectedSession(); + protectedSessionHolder.resetProtectedSession(); } else { utils.showError(result.message); @@ -105,7 +105,7 @@ addModule((function() { const protectedSessionTimeout = $protectedSessionTimeout.val(); settings.saveSettings(settingName, protectedSessionTimeout).then(() => { - protectedSessionService.setProtectedSessionTimeout(protectedSessionTimeout); + protectedSessionHolder.setProtectedSessionTimeout(protectedSessionTimeout); }); return false; diff --git a/src/public/javascripts/services/bundle.js b/src/public/javascripts/services/bundle.js new file mode 100644 index 000000000..cdd655269 --- /dev/null +++ b/src/public/javascripts/services/bundle.js @@ -0,0 +1,13 @@ +import ScriptContext from "./script_context"; + +async function executeBundle(bundle) { + const apiContext = ScriptContext(bundle.note, bundle.allNotes); + + return await (function () { + return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`); + }.call(apiContext)); +} + +export default { + executeBundle +} \ No newline at end of file diff --git a/src/public/javascripts/services/export.js b/src/public/javascripts/services/export.js index 5018dc384..d945bfc1e 100644 --- a/src/public/javascripts/services/export.js +++ b/src/public/javascripts/services/export.js @@ -1,10 +1,10 @@ import treeService from './tree.js'; -import protectedSessionService from './protected_session.js'; +import protectedSessionHolder from './protected_session_holder.js'; import utils from './utils.js'; function exportSubTree(noteId) { const url = utils.getHost() + "/api/export/" + noteId + "?protectedSessionId=" - + encodeURIComponent(protectedSessionService.getProtectedSessionId()); + + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); utils.download(url); } diff --git a/src/public/javascripts/services/init.js b/src/public/javascripts/services/init.js index 0a7bc0142..67002d996 100644 --- a/src/public/javascripts/services/init.js +++ b/src/public/javascripts/services/init.js @@ -5,6 +5,7 @@ import noteDetailService from './note_detail.js'; import treeUtils from './tree_utils.js'; import utils from './utils.js'; import server from './server.js'; +import bundleService from './bundle.js'; // hot keys are active also inside inputs and content editables jQuery.hotkeys.options.filterInputAcceptingElements = false; @@ -209,7 +210,7 @@ $("#logout-button").toggle(!utils.isElectron()); $(document).ready(() => { server.get("script/startup").then(scriptBundles => { for (const bundle of scriptBundles) { - utils.executeBundle(bundle); + bundleService.executeBundle(bundle); } }); }); diff --git a/src/public/javascripts/services/messaging.js b/src/public/javascripts/services/messaging.js index b5a930582..dc57ed818 100644 --- a/src/public/javascripts/services/messaging.js +++ b/src/public/javascripts/services/messaging.js @@ -1,10 +1,9 @@ -import treeService from './tree.js'; -import noteDetailService from './note_detail.js'; import utils from './utils.js'; -import recentNotesDialog from '../dialogs/recent_notes.js'; const $changesToPushCount = $("#changes-to-push-count"); +const messageHandlers = []; + let ws; let lastSyncId; let lastPingTs; @@ -21,7 +20,11 @@ function logError(message) { } } -function messageHandler(event) { +function subscribeToMessages(messageHandler) { + messageHandlers.push(messageHandler); +} + +function handleMessage(event) { const message = JSON.parse(event.data); if (message.type === 'sync') { @@ -35,29 +38,10 @@ function messageHandler(event) { const syncData = message.data.filter(sync => sync.sourceId !== glob.sourceId); - if (syncData.some(sync => sync.entityName === 'branches') - || syncData.some(sync => sync.entityName === 'notes')) { - - console.log(utils.now(), "Reloading tree because of background changes"); - - treeService.reload(); + for (const messageHandler of messageHandlers) { + messageHandler(syncData); } - if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === noteDetailService.getCurrentNoteId())) { - utils.showMessage('Reloading note because of background changes'); - - noteDetailService.reload(); - } - - if (syncData.some(sync => sync.entityName === 'recent_notes')) { - console.log(utils.now(), "Reloading recent notes because of background changes"); - - recentNotesDialog.reload(); - } - - // we don't detect image changes here since images themselves are immutable and references should be - // updated in note detail as well - $changesToPushCount.html(message.changesToPushCount); } else if (message.type === 'sync-hash-check-failed') { @@ -74,7 +58,7 @@ function connectWebSocket() { // use wss for secure messaging const ws = new WebSocket(protocol + "://" + location.host); ws.onopen = event => console.log(utils.now(), "Connected to server with WebSocket"); - ws.onmessage = messageHandler; + ws.onmessage = handleMessage; ws.onclose = function(){ // Try to reconnect in 5 seconds setTimeout(() => connectWebSocket(), 5000); @@ -118,5 +102,6 @@ setTimeout(() => { }, 1000); export default { - logError + logError, + subscribeToMessages }; \ No newline at end of file diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 8c43cda0f..e3a895c6d 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -1,8 +1,11 @@ import treeService from './tree.js'; import noteTypeService from './note_type.js'; import protectedSessionService from './protected_session.js'; +import protectedSessionHolder from './protected_session_holder.js'; import utils from './utils.js'; import server from './server.js'; +import messagingService from "./messaging.js"; +import bundleService from "./bundle.js"; const $noteTitle = $("#note-title"); @@ -78,7 +81,7 @@ async function saveNoteIfChanged() { await saveNoteToServer(note); if (note.detail.isProtected) { - protectedSessionService.touchProtectedSession(); + protectedSessionHolder.touchProtectedSession(); } } @@ -222,7 +225,7 @@ async function loadNoteToEditor(noteId) { await protectedSessionService.ensureProtectedSession(currentNote.detail.isProtected, false); if (currentNote.detail.isProtected) { - protectedSessionService.touchProtectedSession(); + protectedSessionHolder.touchProtectedSession(); } // this might be important if we focused on protected note when not in protected note and we got a dialog @@ -251,7 +254,7 @@ async function loadNoteToEditor(noteId) { $noteDetailRender.html(bundle.html); - utils.executeBundle(bundle); + bundleService.executeBundle(bundle); } else if (currentNote.detail.type === 'file') { $noteDetailAttachment.show(); @@ -333,7 +336,7 @@ async function executeCurrentNote() { if (currentNote.detail.mime.endsWith("env=frontend")) { const bundle = await server.get('script/bundle/' + getCurrentNoteId()); - utils.executeBundle(bundle); + bundleService.executeBundle(bundle); } if (currentNote.detail.mime.endsWith("env=backend")) { @@ -360,9 +363,17 @@ $attachmentOpen.click(() => { function getAttachmentUrl() { // electron needs absolute URL so we extract current host, port, protocol return utils.getHost() + "/api/attachments/download/" + getCurrentNoteId() - + "?protectedSessionId=" + encodeURIComponent(protectedSessionService.getProtectedSessionId()); + + "?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId()); } +messagingService.subscribeToMessages(syncData => { + if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) { + utils.showMessage('Reloading note because of background changes'); + + reload(); + } +}); + $(document).ready(() => { $noteTitle.on('input', () => { noteChanged(); diff --git a/src/public/javascripts/services/protected_session.js b/src/public/javascripts/services/protected_session.js index ae67a4508..a9afdfb5c 100644 --- a/src/public/javascripts/services/protected_session.js +++ b/src/public/javascripts/services/protected_session.js @@ -2,6 +2,7 @@ import treeService from './tree.js'; import noteDetail from './note_detail.js'; import utils from './utils.js'; import server from './server.js'; +import protectedSessionHolder from './protected_session_holder.js'; const $dialog = $("#protected-session-password-dialog"); const $passwordForm = $("#protected-session-password-form"); @@ -11,22 +12,11 @@ const $protectButton = $("#protect-button"); const $unprotectButton = $("#unprotect-button"); let protectedSessionDeferred = null; -let lastProtectedSessionOperationDate = null; -let protectedSessionTimeout = null; -let protectedSessionId = null; - -$(document).ready(() => { - server.get('settings/all').then(settings => protectedSessionTimeout = settings.protected_session_timeout); -}); - -function setProtectedSessionTimeout(encSessTimeout) { - protectedSessionTimeout = encSessTimeout; -} function ensureProtectedSession(requireProtectedSession, modal) { const dfd = $.Deferred(); - if (requireProtectedSession && !isProtectedSessionAvailable()) { + if (requireProtectedSession && !protectedSessionHolder.isProtectedSessionAvailable()) { protectedSessionDeferred = dfd; if (treeService.getCurrentNode().data.isProtected) { @@ -62,7 +52,7 @@ async function setupProtectedSession() { return; } - protectedSessionId = response.protectedSessionId; + protectedSessionHolder.setProtectedSessionId(response.protectedSessionId); $dialog.dialog("close"); @@ -96,22 +86,6 @@ async function enterProtectedSession(password) { }); } -function getProtectedSessionId() { - return protectedSessionId; -} - -function resetProtectedSession() { - protectedSessionId = null; - - // most secure solution - guarantees nothing remained in memory - // since this expires because user doesn't use the app, it shouldn't be disruptive - utils.reloadApp(); -} - -function isProtectedSessionAvailable() { - return protectedSessionId !== null; -} - async function protectNoteAndSendToServer() { await ensureProtectedSession(true, true); @@ -144,12 +118,6 @@ async function unprotectNoteAndSendToServer() { noteDetail.setNoteBackgroundIfProtected(note); } -function touchProtectedSession() { - if (isProtectedSessionAvailable()) { - lastProtectedSessionOperationDate = new Date(); - } -} - async function protectSubTree(noteId, protect) { await ensureProtectedSession(true, true); @@ -167,24 +135,13 @@ $passwordForm.submit(() => { return false; }); -setInterval(() => { - if (lastProtectedSessionOperationDate !== null && new Date().getTime() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { - resetProtectedSession(); - } -}, 5000); - $protectButton.click(protectNoteAndSendToServer); $unprotectButton.click(unprotectNoteAndSendToServer); export default { - setProtectedSessionTimeout, ensureProtectedSession, - resetProtectedSession, - isProtectedSessionAvailable, protectNoteAndSendToServer, unprotectNoteAndSendToServer, - getProtectedSessionId, - touchProtectedSession, protectSubTree, ensureDialogIsClosed }; \ No newline at end of file diff --git a/src/public/javascripts/services/protected_session_holder.js b/src/public/javascripts/services/protected_session_holder.js new file mode 100644 index 000000000..9d5595c70 --- /dev/null +++ b/src/public/javascripts/services/protected_session_holder.js @@ -0,0 +1,55 @@ +import utils from "./utils.js"; +import server from "./server.js"; + +let lastProtectedSessionOperationDate = null; +let protectedSessionTimeout = null; +let protectedSessionId = null; + +$(document).ready(() => { + server.get('settings/all').then(settings => protectedSessionTimeout = settings.protected_session_timeout); +}); + +setInterval(() => { + if (lastProtectedSessionOperationDate !== null && new Date().getTime() - lastProtectedSessionOperationDate.getTime() > protectedSessionTimeout * 1000) { + resetProtectedSession(); + } +}, 5000); + +function setProtectedSessionTimeout(encSessTimeout) { + protectedSessionTimeout = encSessTimeout; +} + +function getProtectedSessionId() { + return protectedSessionId; +} + +function setProtectedSessionId(id) { + protectedSessionId = id; +} + +function resetProtectedSession() { + protectedSessionId = null; + + // most secure solution - guarantees nothing remained in memory + // since this expires because user doesn't use the app, it shouldn't be disruptive + utils.reloadApp(); +} + +function isProtectedSessionAvailable() { + return protectedSessionId !== null; +} + +function touchProtectedSession() { + if (isProtectedSessionAvailable()) { + lastProtectedSessionOperationDate = new Date(); + } +} + +export default { + getProtectedSessionId, + setProtectedSessionId, + resetProtectedSession, + isProtectedSessionAvailable, + setProtectedSessionTimeout, + touchProtectedSession +}; \ No newline at end of file diff --git a/src/public/javascripts/services/server.js b/src/public/javascripts/services/server.js index 133737395..c777a7fe0 100644 --- a/src/public/javascripts/services/server.js +++ b/src/public/javascripts/services/server.js @@ -1,11 +1,11 @@ -import protectedSessionService from './protected_session.js'; +import protectedSessionHolder from './protected_session_holder.js'; import utils from './utils.js'; function getHeaders() { let protectedSessionId = null; try { // this is because protected session might not be declared in some cases - like when it's included in migration page - protectedSessionId = protectedSessionService.getProtectedSessionId(); + protectedSessionId = protectedSessionHolder.getProtectedSessionId(); } catch(e) {} diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index a0ca98af1..e98e6255b 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -3,7 +3,7 @@ import dragAndDropSetup from './drag_and_drop.js'; import linkService from './link.js'; import messagingService from './messaging.js'; import noteDetailService from './note_detail.js'; -import protectedSessionService from './protected_session.js'; +import protectedSessionHolder from './protected_session_holder.js'; import treeChangesService from './tree_changes.js'; import treeUtils from './tree_utils.js'; import utils from './utils.js'; @@ -777,7 +777,7 @@ async function createNote(node, parentNoteId, target, isProtected) { // if isProtected isn't available (user didn't enter password yet), then note is created as unencrypted // but this is quite weird since user doesn't see WHERE the note is being created so it shouldn't occur often - if (!isProtected || !protectedSessionService.isProtectedSessionAvailable()) { + if (!isProtected || !protectedSessionHolder.isProtectedSessionAvailable()) { isProtected = false; } @@ -857,6 +857,16 @@ async function getBranch(branchId) { return await treeCache.getBranch(branchId); } +messagingService.subscribeToMessages(syncData => { + if (syncData.some(sync => sync.entityName === 'branches') + || syncData.some(sync => sync.entityName === 'notes')) { + + console.log(utils.now(), "Reloading tree because of background changes"); + + reload(); + } +}); + $(document).bind('keydown', 'ctrl+o', e => { const node = getCurrentNode(); const parentNoteId = node.data.parentNoteId; diff --git a/src/public/javascripts/services/utils.js b/src/public/javascripts/services/utils.js index 44763f91e..109446126 100644 --- a/src/public/javascripts/services/utils.js +++ b/src/public/javascripts/services/utils.js @@ -1,5 +1,4 @@ -//import messagingService from './messaging.js'; -//import ScriptContext from './script_context.js'; +import messagingService from './messaging.js'; function reloadApp() { window.location.reload(true); @@ -116,14 +115,6 @@ async function stopWatch(what, func) { return ret; } -async function executeBundle(bundle) { - const apiContext = ScriptContext(bundle.note, bundle.allNotes); - - return await (function () { - return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`); - }.call(apiContext)); -} - function formatValueWithWhitespace(val) { return /[^\w_-]/.test(val) ? '"' + val + '"' : val; } @@ -263,7 +254,6 @@ export default { isRootNode, escapeHtml, stopWatch, - executeBundle, formatValueWithWhitespace, formatLabel, requireLibrary,