diff --git a/app.js b/app.js index 40e9d5957..77165068a 100644 --- a/app.js +++ b/app.js @@ -9,6 +9,7 @@ const session = require('express-session'); const FileStore = require('session-file-store')(session); const os = require('os'); const sessionSecret = require('./services/session_secret'); +const utils = require('./services/utils'); require('./services/ping_job'); @@ -71,4 +72,30 @@ require('./services/sync'); // triggers backup timer require('./services/backup'); +if (utils.isElectron()) { + const ipcMain = require('electron').ipcMain; + + ipcMain.on('server-request', (event, arg) => { + const req = {}; + req.url = arg.url; + req.method = arg.method; + req.body = arg.data; + req.headers = {}; + + const res = {}; + res.setHeader = function() { + + }; + + res.send = function(obj) { + event.sender.send('server-response', { + requestId: arg.requestId, + body: obj + }); + }; + + return app._router.handle(req, res, () => {}); + }); +} + module.exports = app; \ No newline at end of file diff --git a/public/javascripts/dialogs/edit_tree_prefix.js b/public/javascripts/dialogs/edit_tree_prefix.js index 5ae40decf..9ec3c41e4 100644 --- a/public/javascripts/dialogs/edit_tree_prefix.js +++ b/public/javascripts/dialogs/edit_tree_prefix.js @@ -30,16 +30,9 @@ const editTreePrefix = (function() { formEl.submit(() => { const prefix = treePrefixInputEl.val(); - $.ajax({ - url: baseApiUrl + 'tree/' + noteTreeId + '/setPrefix', - type: 'PUT', - contentType: 'application/json', - data: JSON.stringify({ - prefix: prefix - }), - success: () => noteTree.setPrefix(noteTreeId, prefix), - error: () => showError("Error setting prefix.") - }); + server.put('tree/' + noteTreeId + '/setPrefix', { + prefix: prefix + }).then(() => noteTree.setPrefix(noteTreeId, prefix)); dialogEl.dialog("close"); diff --git a/public/javascripts/dialogs/event_log.js b/public/javascripts/dialogs/event_log.js index bd650eb05..428bf1a41 100644 --- a/public/javascripts/dialogs/event_log.js +++ b/public/javascripts/dialogs/event_log.js @@ -13,11 +13,7 @@ const eventLog = (function() { height: 700 }); - const result = await $.ajax({ - url: baseApiUrl + 'event-log', - type: 'GET', - error: () => showError("Error getting event log.") - }); + const result = await server.get('event-log'); listEl.html(''); diff --git a/public/javascripts/dialogs/note_history.js b/public/javascripts/dialogs/note_history.js index 7254e6f58..a6da3321b 100644 --- a/public/javascripts/dialogs/note_history.js +++ b/public/javascripts/dialogs/note_history.js @@ -24,11 +24,7 @@ const noteHistory = (function() { listEl.empty(); contentEl.empty(); - historyItems = await $.ajax({ - url: baseApiUrl + 'notes-history/' + noteId, - type: 'GET', - error: () => showError("Error getting note history.") - }); + historyItems = await server.get('notes-history/' + noteId); for (const item of historyItems) { const dateModified = getDateFromTS(item.date_modified_to); diff --git a/public/javascripts/dialogs/recent_changes.js b/public/javascripts/dialogs/recent_changes.js index a1ab34bce..d2547dddb 100644 --- a/public/javascripts/dialogs/recent_changes.js +++ b/public/javascripts/dialogs/recent_changes.js @@ -12,11 +12,7 @@ const recentChanges = (function() { height: 700 }); - const result = await $.ajax({ - url: baseApiUrl + 'recent-changes/', - type: 'GET', - error: () => showError("Error getting recent changes.") - }); + const result = await server.get('recent-changes/'); dialogEl.html(''); diff --git a/public/javascripts/dialogs/recent_notes.js b/public/javascripts/dialogs/recent_notes.js index eaa80d95f..a1efcbbff 100644 --- a/public/javascripts/dialogs/recent_notes.js +++ b/public/javascripts/dialogs/recent_notes.js @@ -10,38 +10,26 @@ const recentNotes = (function() { const noteDetailEl = $('#note-detail'); let list = []; - $.ajax({ - url: baseApiUrl + 'recent-notes', - type: 'GET', - error: () => showError("Error getting recent notes.") - }).then(result => { + server.get('recent-notes').then(result => { list = result.map(r => r.note_tree_id); }); function addRecentNote(notePath) { - setTimeout(() => { + setTimeout(async () => { // we include the note into recent list only if the user stayed on the note at least 5 seconds if (notePath && notePath === noteTree.getCurrentNotePath()) { - $.ajax({ - url: baseApiUrl + 'recent-notes/' + encodeURIComponent(notePath), - type: 'PUT', - error: () => showError("Error setting recent notes.") - }).then(result => { - list = result.map(r => r.note_path); - }); + const result = await server.put('recent-notes/' + encodeURIComponent(notePath)); + + list = result.map(r => r.note_path); } }, 1500); } // FIXME: this should be probably just refresh upon deletion, not explicit delete - function removeRecentNote(notePathIdToRemove) { - $.ajax({ - url: baseApiUrl + 'recent-notes/' + encodeURIComponent(notePathIdToRemove), - type: 'DELETE', - error: () => showError("Error removing note from recent notes.") - }).then(result => { - list = result.map(r => r.note_path); - }); + async function removeRecentNote(notePathIdToRemove) { + const result = server.remove('recent-notes/' + encodeURIComponent(notePathIdToRemove)); + + list = result.map(r => r.note_path); } function showDialog() { diff --git a/public/javascripts/dialogs/settings.js b/public/javascripts/dialogs/settings.js index 95bd15bfe..b5b18fb3e 100644 --- a/public/javascripts/dialogs/settings.js +++ b/public/javascripts/dialogs/settings.js @@ -13,11 +13,7 @@ const settings = (function() { async function showDialog() { glob.activeDialog = dialogEl; - const settings = await $.ajax({ - url: baseApiUrl + 'settings', - type: 'GET', - error: () => showError("Error getting settings.") - }); + const settings = await server.get('settings'); dialogEl.dialog({ modal: true, @@ -33,20 +29,13 @@ const settings = (function() { } } - function saveSettings(settingName, settingValue) { - return $.ajax({ - url: baseApiUrl + 'settings', - type: 'POST', - data: JSON.stringify({ - name: settingName, - value: settingValue - }), - contentType: "application/json", - success: () => { - showMessage("Settings change have been saved."); - }, - error: () => alert("Error occurred during saving settings change.") + async function saveSettings(settingName, settingValue) { + await server.post('settings', { + name: settingName, + value: settingValue }); + + showMessage("Settings change have been saved."); } return { @@ -79,26 +68,19 @@ settings.addModule((function() { return false; } - $.ajax({ - url: baseApiUrl + 'password/change', - type: 'POST', - data: JSON.stringify({ - 'current_password': oldPassword, - 'new_password': newPassword1 - }), - contentType: "application/json", - success: result => { - if (result.success) { - alert("Password has been changed. Trilium will be reloaded after you press OK."); + server.post('password/change', { + 'current_password': oldPassword, + 'new_password': newPassword1 + }).then(result => { + if (result.success) { + 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 - protected_session.resetProtectedSession(); - } - else { - showError(result.message); - } - }, - error: () => showError("Error occurred during changing password.") + // password changed so current protected session is invalid and needs to be cleared + protected_session.resetProtectedSession(); + } + else { + showError(result.message); + } }); return false; @@ -159,7 +141,7 @@ settings.addModule((async function () { const buildDateEl = $("#build-date"); const buildRevisionEl = $("#build-revision"); - const appInfo = await $.get(baseApiUrl + 'app-info'); + const appInfo = await server.get('app-info'); appVersionEl.html(appInfo.app_version); dbVersionEl.html(appInfo.db_version); diff --git a/public/javascripts/init.js b/public/javascripts/init.js index 1cb4b3eb9..b51872028 100644 --- a/public/javascripts/init.js +++ b/public/javascripts/init.js @@ -129,14 +129,4 @@ function showAppIfHidden() { // Kick off the CSS transition loaderDiv.style.opacity = 0.0; } -} - -function initAjax() { - $.ajaxSetup({ - headers: { - 'x-protected-session-id': typeof protected_session !== 'undefined' ? protected_session.getProtectedSessionId() : null - } - }); -} - -initAjax(); \ No newline at end of file +} \ No newline at end of file diff --git a/public/javascripts/migration.js b/public/javascripts/migration.js index ca10779e3..0d2456248 100644 --- a/public/javascripts/migration.js +++ b/public/javascripts/migration.js @@ -17,29 +17,28 @@ $(document).ready(() => { }); }); -$("#run-migration").click(() => { +$("#run-migration").click(async () => { $("#run-migration").prop("disabled", true); $("#migration-result").show(); - $.ajax({ + const result = await $.ajax({ url: baseApiUrl + 'migration', type: 'POST', - success: result => { - for (const migration of result.migrations) { - const row = $('') - .append($('').html(migration.db_version)) - .append($('').html(migration.name)) - .append($('').html(migration.success ? 'Yes' : 'No')) - .append($('').html(migration.success ? 'N/A' : migration.error)); - - if (!migration.success) { - row.addClass("danger"); - } - - $("#migration-table").append(row); - } - }, error: () => showError("Migration failed with unknown error") }); + + for (const migration of result.migrations) { + const row = $('') + .append($('').html(migration.db_version)) + .append($('').html(migration.name)) + .append($('').html(migration.success ? 'Yes' : 'No')) + .append($('').html(migration.success ? 'N/A' : migration.error)); + + if (!migration.success) { + row.addClass("danger"); + } + + $("#migration-table").append(row); + } }); \ No newline at end of file diff --git a/public/javascripts/note_editor.js b/public/javascripts/note_editor.js index c0015843a..6053e3ea5 100644 --- a/public/javascripts/note_editor.js +++ b/public/javascripts/note_editor.js @@ -93,15 +93,7 @@ const noteEditor = (function() { } async function saveNoteToServer(note) { - await $.ajax({ - url: baseApiUrl + 'notes/' + note.detail.note_id, - type: 'PUT', - data: JSON.stringify(note), - contentType: "application/json", - error: () => { - showError("Error saving the note!"); - } - }); + await server.put('notes/' + note.detail.note_id, note); isNoteChanged = false; @@ -130,7 +122,7 @@ const noteEditor = (function() { } async function loadNoteToEditor(noteId) { - currentNote = await $.get(baseApiUrl + 'notes/' + noteId); + currentNote = await server.get('notes/' + noteId); if (isNewNoteCreated) { isNewNoteCreated = false; @@ -167,7 +159,7 @@ const noteEditor = (function() { } async function loadNote(noteId) { - return await $.get(baseApiUrl + 'notes/' + noteId); + return await server.get('notes/' + noteId); } $(document).ready(() => { diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js index c52af4a12..d193f8f13 100644 --- a/public/javascripts/note_tree.js +++ b/public/javascripts/note_tree.js @@ -307,15 +307,10 @@ const noteTree = (function() { return path.reverse().join('/'); } - function setExpandedToServer(noteTreeId, isExpanded) { + async function setExpandedToServer(noteTreeId, isExpanded) { const expandedNum = isExpanded ? 1 : 0; - $.ajax({ - url: baseApiUrl + 'notes/' + noteTreeId + '/expanded/' + expandedNum, - type: 'PUT', - contentType: "application/json", - success: result => {} - }); + await server.put('notes/' + noteTreeId + '/expanded/' + expandedNum); } function setCurrentNotePathToHash(node) { @@ -479,7 +474,7 @@ const noteTree = (function() { } function loadTree() { - return $.get(baseApiUrl + 'tree').then(resp => { + return server.get('tree').then(resp => { startNoteTreeId = resp.start_note_tree_id; treeLoadTime = resp.tree_load_time; @@ -574,16 +569,11 @@ const noteTree = (function() { const newNoteName = "new note"; - const result = await $.ajax({ - url: baseApiUrl + 'notes/' + parentNoteId + '/children' , - type: 'POST', - data: JSON.stringify({ - note_title: newNoteName, - target: target, - target_note_tree_id: node.data.note_tree_id, - is_protected: isProtected - }), - contentType: "application/json" + const result = await server.post('notes/' + parentNoteId + '/children', { + note_title: newNoteName, + target: target, + target_note_tree_id: node.data.note_tree_id, + is_protected: isProtected }); const newNode = { diff --git a/public/javascripts/protected_session.js b/public/javascripts/protected_session.js index 58aef9d39..a00a677a9 100644 --- a/public/javascripts/protected_session.js +++ b/public/javascripts/protected_session.js @@ -10,11 +10,7 @@ const protected_session = (function() { let protectedSessionTimeout = null; let protectedSessionId = null; - $.ajax({ - url: baseApiUrl + 'settings/all', - type: 'GET', - error: () => showError("Error getting protected session settings.") - }).then(settings => { + server.get('settings/all').then(settings => { protectedSessionTimeout = settings.protected_session_timeout; }); @@ -61,7 +57,7 @@ const protected_session = (function() { } protectedSessionId = response.protectedSessionId; - initAjax(); + server.initAjax(); dialogEl.dialog("close"); @@ -88,14 +84,8 @@ const protected_session = (function() { } async function enterProtectedSession(password) { - return await $.ajax({ - url: baseApiUrl + 'login/protected', - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ - password: password - }), - error: () => showError("Error entering protected session.") + return await server.post('login/protected', { + password: password }); } @@ -106,7 +96,7 @@ const protected_session = (function() { function resetProtectedSession() { protectedSessionId = null; - initAjax(); + server.initAjax(); // most secure solution - guarantees nothing remained in memory // since this expires because user doesn't use the app, it shouldn't be disruptive @@ -154,12 +144,7 @@ const protected_session = (function() { async function protectSubTree(noteId, protect) { await ensureProtectedSession(true, true); - await $.ajax({ - url: baseApiUrl + 'tree/' + noteId + "/protectSubTree/" + (protect ? 1 : 0), - type: 'PUT', - contentType: 'application/json', - error: () => showError("Request to un/protect sub tree has failed.") - }); + await server.put('tree/' + noteId + "/protectSubTree/" + (protect ? 1 : 0)); showMessage("Request to un/protect sub tree has finished successfully"); diff --git a/public/javascripts/search_tree.js b/public/javascripts/search_tree.js index 60279776e..38e85d39c 100644 --- a/public/javascripts/search_tree.js +++ b/public/javascripts/search_tree.js @@ -43,7 +43,7 @@ const searchTree = (function() { } if (e && e.which === $.ui.keyCode.ENTER) { - $.get(baseApiUrl + 'notes?search=' + searchText).then(resp => { + server.get('notes?search=' + searchText).then(resp => { // Pass a string to perform case insensitive matching getTree().filterBranches(node => { return resp.includes(node.data.note_id); diff --git a/public/javascripts/server.js b/public/javascripts/server.js new file mode 100644 index 000000000..e8f53bf27 --- /dev/null +++ b/public/javascripts/server.js @@ -0,0 +1,84 @@ +const server = (function() { + function initAjax() { + $.ajaxSetup({ + headers: { + 'x-protected-session-id': typeof protected_session !== 'undefined' ? protected_session.getProtectedSessionId() : null + } + }); + } + + async function get(url) { + return await call('GET', url); + } + + async function post(url, data) { + return await call('POST', url, data); + } + + async function put(url, data) { + return await call('PUT', url, data); + } + + async function remove(url) { + return await call('DELETE', url); + } + + let i = 1; + const reqResolves = {}; + + async function call(method, url, data) { + if (isElectron()) { + const ipc = require('electron').ipcRenderer; + const requestId = i++; + + return new Promise((resolve, reject) => { + reqResolves[requestId] = resolve; + + ipc.send('server-request', { + requestId: requestId, + method: method, + url: "/" + baseApiUrl + url, + data: data + }); + }); + } + else { + return await ajax(url, method, data); + } + } + + if (isElectron()) { + const ipc = require('electron').ipcRenderer; + + ipc.on('server-response', (event, arg) => { + reqResolves[arg.requestId](arg.body); + }); + } + + async function ajax(url, method, data) { + const options = { + url: baseApiUrl + url, + type: method + }; + + if (data) { + options.data = JSON.stringify(data); + options.contentType = "application/json"; + } + + return await $.ajax(options).catch(e => { + showError("Error when calling " + method + " " + url + ": " + e); + }); + } + + + initAjax(); + + return { + get, + post, + put, + remove, + initAjax + } +})(); \ No newline at end of file diff --git a/public/javascripts/sync.js b/public/javascripts/sync.js index 38aa7f6ad..49f2b33f1 100644 --- a/public/javascripts/sync.js +++ b/public/javascripts/sync.js @@ -1,21 +1,16 @@ "use strict"; -function syncNow() { - $.ajax({ - url: baseApiUrl + 'sync/now', - type: 'POST', - success: result => { - if (result.success) { - showMessage("Sync finished successfully."); - } - else { - if (result.message.length > 50) { - result.message = result.message.substr(0, 50); - } +async function syncNow() { + const result = await server.post('sync/now'); - showError("Sync failed: " + result.message); - } - }, - error: () => showError("Sync failed for unknown reason.") - }); + if (result.success) { + showMessage("Sync finished successfully."); + } + else { + if (result.message.length > 50) { + result.message = result.message.substr(0, 50); + } + + showError("Sync failed: " + result.message); + } } \ No newline at end of file diff --git a/public/javascripts/tree_changes.js b/public/javascripts/tree_changes.js index 0dc4cac35..26f6ea5f4 100644 --- a/public/javascripts/tree_changes.js +++ b/public/javascripts/tree_changes.js @@ -2,11 +2,7 @@ const treeChanges = (function() { async function moveBeforeNode(node, beforeNode, changeInPath = true) { - await $.ajax({ - url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveBefore/' + beforeNode.data.note_tree_id, - type: 'PUT', - contentType: "application/json" - }); + await server.put('notes/' + node.data.note_tree_id + '/moveBefore/' + beforeNode.data.note_tree_id); node.moveTo(beforeNode, 'before'); @@ -18,11 +14,7 @@ const treeChanges = (function() { } async function moveAfterNode(node, afterNode, changeInPath = true) { - await $.ajax({ - url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveAfter/' + afterNode.data.note_tree_id, - type: 'PUT', - contentType: "application/json" - }); + await server.put('notes/' + node.data.note_tree_id + '/moveAfter/' + afterNode.data.note_tree_id); node.moveTo(afterNode, 'after'); @@ -35,11 +27,7 @@ const treeChanges = (function() { // beware that first arg is noteId and second is noteTreeId! async function cloneNoteAfter(noteId, afterNoteTreeId) { - const resp = await $.ajax({ - url: baseApiUrl + 'notes/' + noteId + '/cloneAfter/' + afterNoteTreeId, - type: 'PUT', - error: () => showError("Error cloning note.") - }); + const resp = await server.put('notes/' + noteId + '/cloneAfter/' + afterNoteTreeId); if (!resp.success) { alert(resp.message); @@ -50,11 +38,7 @@ const treeChanges = (function() { } async function moveToNode(node, toNode) { - await $.ajax({ - url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveTo/' + toNode.data.note_id, - type: 'PUT', - contentType: "application/json" - }); + await server.put('notes/' + node.data.note_tree_id + '/moveTo/' + toNode.data.note_id); node.moveTo(toNode); @@ -69,11 +53,7 @@ const treeChanges = (function() { } async function cloneNoteTo(childNoteId, parentNoteId) { - const resp = await $.ajax({ - url: baseApiUrl + 'notes/' + childNoteId + '/cloneTo/' + parentNoteId, - type: 'PUT', - error: () => showError("Error cloning note.") - }); + const resp = await server.put('notes/' + childNoteId + '/cloneTo/' + parentNoteId); if (!resp.success) { alert(resp.message); @@ -88,10 +68,7 @@ const treeChanges = (function() { return; } - await $.ajax({ - url: baseApiUrl + 'notes/' + node.data.note_tree_id, - type: 'DELETE' - }); + await server.remove('notes/' + node.data.note_tree_id); if (node.getParent() !== null && node.getParent().getChildren().length <= 1) { node.getParent().folder = false; @@ -118,11 +95,7 @@ const treeChanges = (function() { return; } - await $.ajax({ - url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveAfter/' + node.getParent().data.note_tree_id, - type: 'PUT', - contentType: "application/json", - }); + await server.put('notes/' + node.data.note_tree_id + '/moveAfter/' + node.getParent().data.note_tree_id); if (node.getParent() !== null && node.getParent().getChildren().length <= 1) { node.getParent().folder = false; diff --git a/services/sync.js b/services/sync.js index ca53d7a6b..9277a4ba6 100644 --- a/services/sync.js +++ b/services/sync.js @@ -105,6 +105,12 @@ async function getLastSyncedPull() { return parseInt(await options.getOption('last_synced_pull')); } +async function setLastSyncedPull(syncId) { + await sql.doInTransaction(async () => { + await options.setOption('last_synced_pull', syncId); + }); +} + async function pullSync(syncContext) { const lastSyncedPull = await getLastSyncedPull(); @@ -118,6 +124,8 @@ async function pullSync(syncContext) { if (source_id.isLocalSourceId(sync.source_id)) { log.info("Skipping " + sync.entity_name + " " + sync.entity_id + " because it has local source id."); + await setLastSyncedPull(sync.id); + continue; } @@ -149,9 +157,7 @@ async function pullSync(syncContext) { throw new Error("Unrecognized entity type " + sync.entity_name); } - await sql.doInTransaction(async () => { - await options.setOption('last_synced_pull', sync.id); - }); + await setLastSyncedPull(sync.id); } log.info("Finished pull"); diff --git a/views/index.ejs b/views/index.ejs index ff03ad98d..5e304aa20 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -302,6 +302,7 @@ +