per-browser source id so we support having notecase opened in multiple tabs/windows

This commit is contained in:
azivner 2017-12-16 20:48:34 -05:00
parent 03d86209ca
commit 50ff5da947
17 changed files with 98 additions and 83 deletions

View file

@ -1,9 +1,5 @@
"use strict";
const glob = {
activeDialog: null
};
// hot keys are active also inside inputs and content editables
jQuery.hotkeys.options.filterInputAcceptingElements = true;
jQuery.hotkeys.options.filterContentEditable = true;

View file

@ -19,23 +19,27 @@ const messaging = (function() {
function messageHandler(event) {
const message = JSON.parse(event.data);
if (message.data.length > 0) {
console.log(message);
}
if (message.type === 'sync') {
lastPingTs = new Date().getTime();
const data = message.data;
const syncData = message.data.filter(sync => sync.source_id !== glob.sourceId);
if (data.notes_tree) {
if (syncData.some(sync => sync.entity_name === 'notes_tree')) {
console.log("Reloading tree because of background changes");
noteTree.reload();
}
if (data.notes && data.notes.includes(noteEditor.getCurrentNoteId())) {
if (syncData.some(sync => sync.entity_name === 'notes' && sync.entity_id === noteEditor.getCurrentNoteId())) {
showMessage('Reloading note because background change');
noteEditor.reload();
}
if (data.recent_notes) {
if (syncData.some(sync => sync.entity_name === 'recent_notes')) {
console.log("Reloading recent notes because of background changes");
recentNotes.reload();

View file

@ -8,7 +8,8 @@ const server = (function() {
catch(e) {}
return {
'x-protected-session-id': protectedSessionId
protected_session_id: protectedSessionId,
source_id: glob.sourceId
};
}

View file

@ -25,10 +25,12 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => {
});
router.put('', auth.checkApiAuth, async (req, res, next) => {
const sourceId = req.headers.source_id;
await sql.doInTransaction(async () => {
await sql.replace("notes_history", req.body);
await sync_table.addNoteHistorySync(req.body.note_history_id);
await sync_table.addNoteHistorySync(req.body.note_history_id, sourceId);
});
res.send();

View file

@ -35,10 +35,11 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => {
});
router.post('/:parentNoteId/children', async (req, res, next) => {
const sourceId = req.headers.source_id;
const parentNoteId = req.params.parentNoteId;
const note = req.body;
const { noteId, noteTreeId } = await notes.createNewNote(parentNoteId, note);
const { noteId, noteTreeId } = await notes.createNewNote(parentNoteId, note, sourceId);
res.send({
'note_id': noteId,
@ -58,7 +59,7 @@ router.put('/:noteId', async (req, res, next) => {
router.delete('/:noteTreeId', async (req, res, next) => {
await sql.doInTransaction(async () => {
await notes.deleteNote(req.params.noteTreeId);
await notes.deleteNote(req.params.noteTreeId, req.headers.source_id);
});
res.send({});

View file

@ -10,6 +10,7 @@ const sync_table = require('../../services/sync_table');
router.put('/:noteTreeId/move-to/:parentNoteId', auth.checkApiAuth, async (req, res, next) => {
const noteTreeId = req.params.noteTreeId;
const parentNoteId = req.params.parentNoteId;
const sourceId = req.headers.source_id;
const maxNotePos = await sql.getSingleValue('SELECT MAX(note_pos) FROM notes_tree WHERE note_pid = ? AND is_deleted = 0', [parentNoteId]);
const newNotePos = maxNotePos === null ? 0 : maxNotePos + 1;
@ -20,7 +21,7 @@ router.put('/:noteTreeId/move-to/:parentNoteId', auth.checkApiAuth, async (req,
await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[parentNoteId, newNotePos, now, noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteTreeSync(noteTreeId, sourceId);
});
res.send({});
@ -29,6 +30,7 @@ router.put('/:noteTreeId/move-to/:parentNoteId', auth.checkApiAuth, async (req,
router.put('/:noteTreeId/move-before/:beforeNoteTreeId', async (req, res, next) => {
const noteTreeId = req.params.noteTreeId;
const beforeNoteTreeId = req.params.beforeNoteTreeId;
const sourceId = req.headers.source_id;
const beforeNote = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [beforeNoteTreeId]);
@ -38,14 +40,14 @@ router.put('/:noteTreeId/move-before/:beforeNoteTreeId', async (req, res, next)
await sql.execute("UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos >= ? AND is_deleted = 0",
[beforeNote.note_pid, beforeNote.note_pos]);
await sync_table.addNoteReorderingSync(beforeNote.note_pid);
await sync_table.addNoteReorderingSync(beforeNote.note_pid, sourceId);
const now = utils.nowDate();
await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[beforeNote.note_pid, beforeNote.note_pos, now, noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteTreeSync(noteTreeId, sourceId);
});
res.send({});
@ -58,6 +60,7 @@ router.put('/:noteTreeId/move-before/:beforeNoteTreeId', async (req, res, next)
router.put('/:noteTreeId/move-after/:afterNoteTreeId', async (req, res, next) => {
const noteTreeId = req.params.noteTreeId;
const afterNoteTreeId = req.params.afterNoteTreeId;
const sourceId = req.headers.source_id;
const afterNote = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [afterNoteTreeId]);
@ -67,12 +70,12 @@ router.put('/:noteTreeId/move-after/:afterNoteTreeId', async (req, res, next) =>
await sql.execute("UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0",
[afterNote.note_pid, afterNote.note_pos]);
await sync_table.addNoteReorderingSync(afterNote.note_pid);
await sync_table.addNoteReorderingSync(afterNote.note_pid, sourceId);
await sql.execute("UPDATE notes_tree SET note_pid = ?, note_pos = ?, date_modified = ? WHERE note_tree_id = ?",
[afterNote.note_pid, afterNote.note_pos + 1, utils.nowDate(), noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteTreeSync(noteTreeId, sourceId);
});
res.send({});
@ -85,6 +88,7 @@ router.put('/:noteTreeId/move-after/:afterNoteTreeId', async (req, res, next) =>
router.put('/:childNoteId/clone-to/:parentNoteId', auth.checkApiAuth, async (req, res, next) => {
const parentNoteId = req.params.parentNoteId;
const childNoteId = req.params.childNoteId;
const sourceId = req.headers.source_id;
const existing = await sql.getSingleValue('SELECT * FROM notes_tree WHERE note_id = ? AND note_pid = ?', [childNoteId, parentNoteId]);
@ -118,7 +122,7 @@ router.put('/:childNoteId/clone-to/:parentNoteId', auth.checkApiAuth, async (req
await sql.replace("notes_tree", noteTree);
await sync_table.addNoteTreeSync(noteTree.note_tree_id);
await sync_table.addNoteTreeSync(noteTree.note_tree_id, sourceId);
res.send({
success: true
@ -129,6 +133,7 @@ router.put('/:childNoteId/clone-to/:parentNoteId', auth.checkApiAuth, async (req
router.put('/:noteId/clone-after/:afterNoteTreeId', async (req, res, next) => {
const noteId = req.params.noteId;
const afterNoteTreeId = req.params.afterNoteTreeId;
const sourceId = req.headers.source_id;
const afterNote = await sql.getSingleResult("SELECT * FROM notes_tree WHERE note_tree_id = ?", [afterNoteTreeId]);
@ -157,7 +162,7 @@ router.put('/:noteId/clone-after/:afterNoteTreeId', async (req, res, next) => {
await sql.execute("UPDATE notes_tree SET note_pos = note_pos + 1 WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0",
[afterNote.note_pid, afterNote.note_pos]);
await sync_table.addNoteReorderingSync(afterNote.note_pid);
await sync_table.addNoteReorderingSync(afterNote.note_pid, sourceId);
const noteTree = {
note_tree_id: utils.newNoteTreeId(),
@ -171,7 +176,7 @@ router.put('/:noteId/clone-after/:afterNoteTreeId', async (req, res, next) => {
await sql.replace("notes_tree", noteTree);
await sync_table.addNoteTreeSync(noteTree.note_tree_id);
await sync_table.addNoteTreeSync(noteTree.note_tree_id, sourceId);
res.send({
success: true

View file

@ -15,6 +15,7 @@ router.get('', auth.checkApiAuth, async (req, res, next) => {
router.put('/:noteTreeId/:notePath', auth.checkApiAuth, async (req, res, next) => {
const noteTreeId = req.params.noteTreeId;
const notePath = req.params.notePath;
const sourceId = req.headers.source_id;
await sql.doInTransaction(async () => {
await sql.replace('recent_notes', {
@ -24,9 +25,9 @@ router.put('/:noteTreeId/:notePath', auth.checkApiAuth, async (req, res, next) =
is_deleted: 0
});
await sync_table.addRecentNoteSync(noteTreeId);
await sync_table.addRecentNoteSync(noteTreeId, sourceId);
await options.setOption('start_note_path', notePath);
await options.setOption('start_note_path', notePath, sourceId);
});
res.send(await getRecentNotes());

View file

@ -25,12 +25,13 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => {
router.post('/', async (req, res, next) => {
const body = req.body;
const sourceId = req.headers.source_id;
if (ALLOWED_OPTIONS.includes(body['name'])) {
const optionName = await options.getOption(body['name']);
await sql.doInTransaction(async () => {
await options.setOption(body['name'], body['value']);
await options.setOption(body['name'], body['value'], sourceId);
});
res.send({});

View file

@ -39,9 +39,10 @@ router.put('/:noteId/protect-sub-tree/:isProtected', auth.checkApiAuth, async (r
const noteId = req.params.noteId;
const isProtected = !!parseInt(req.params.isProtected);
const dataKey = protected_session.getDataKey(req);
const sourceId = req.headers.source_id;
await sql.doInTransaction(async () => {
await notes.protectNoteRecursively(noteId, dataKey, isProtected);
await notes.protectNoteRecursively(noteId, dataKey, isProtected, sourceId);
});
res.send({});
@ -49,12 +50,13 @@ router.put('/:noteId/protect-sub-tree/:isProtected', auth.checkApiAuth, async (r
router.put('/:noteTreeId/set-prefix', auth.checkApiAuth, async (req, res, next) => {
const noteTreeId = req.params.noteTreeId;
const sourceId = req.headers.source_id;
const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix;
await sql.doInTransaction(async () => {
await sql.execute("UPDATE notes_tree SET prefix = ?, date_modified = ? WHERE note_tree_id = ?", [prefix, utils.nowDate(), noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteTreeSync(noteTreeId, sourceId);
});
res.send({});

View file

@ -3,10 +3,12 @@
const express = require('express');
const router = express.Router();
const auth = require('../services/auth');
const utils = require('../services/utils');
const source_id = require('../services/source_id');
router.get('', auth.checkAuth, async (req, res, next) => {
res.render('index', {});
res.render('index', {
sourceId: await source_id.generateSourceId()
});
});
module.exports = router;

View file

@ -5,7 +5,7 @@ const notes = require('./notes');
const data_encryption = require('./data_encryption');
const sync_table = require('./sync_table');
async function createNewNote(parentNoteId, note) {
async function createNewNote(parentNoteId, note, sourceId) {
const noteId = utils.newNoteId();
const noteTreeId = utils.newNoteTreeId();
@ -25,7 +25,7 @@ async function createNewNote(parentNoteId, note) {
await sql.execute('UPDATE notes_tree SET note_pos = note_pos + 1, date_modified = ? WHERE note_pid = ? AND note_pos > ? AND is_deleted = 0',
[utils.nowDate(), parentNoteId, afterNote.note_pos]);
await sync_table.addNoteReorderingSync(parentNoteId);
await sync_table.addNoteReorderingSync(parentNoteId, sourceId);
}
else {
throw new Error('Unknown target: ' + note.target);
@ -42,7 +42,7 @@ async function createNewNote(parentNoteId, note) {
is_protected: note.is_protected
});
await sync_table.addNoteSync(noteId);
await sync_table.addNoteSync(noteId, sourceId);
await sql.insert("notes_tree", {
note_tree_id: noteTreeId,
@ -54,7 +54,7 @@ async function createNewNote(parentNoteId, note) {
is_deleted: 0
});
await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteTreeSync(noteTreeId, sourceId);
});
return {
@ -68,15 +68,15 @@ async function encryptNote(note, ctx) {
note.detail.note_text = data_encryption.encrypt(ctx.getDataKey(), data_encryption.noteTextIv(note.detail.note_id), note.detail.note_text);
}
async function protectNoteRecursively(noteId, dataKey, protect) {
async function protectNoteRecursively(noteId, dataKey, protect, sourceId) {
const note = await sql.getSingleResult("SELECT * FROM notes WHERE note_id = ?", [noteId]);
await protectNote(note, dataKey, protect);
await protectNote(note, dataKey, protect, sourceId);
const children = await sql.getFlattenedResults("SELECT note_id FROM notes_tree WHERE note_pid = ?", [noteId]);
for (const childNoteId of children) {
await protectNoteRecursively(childNoteId, dataKey, protect);
await protectNoteRecursively(childNoteId, dataKey, protect, sourceId);
}
}
@ -86,7 +86,7 @@ function decryptNote(note, dataKey) {
note.is_protected = false;
}
async function protectNote(note, dataKey, protect) {
async function protectNote(note, dataKey, protect, sourceId) {
let changed = false;
if (protect && !note.is_protected) {
@ -108,13 +108,13 @@ async function protectNote(note, dataKey, protect) {
await sql.execute("UPDATE notes SET note_title = ?, note_text = ?, is_protected = ? WHERE note_id = ?",
[note.note_title, note.note_text, note.is_protected, note.note_id]);
await sync_table.addNoteSync(note.note_id);
await sync_table.addNoteSync(note.note_id, sourceId);
}
await protectNoteHistory(note.note_id, dataKey, protect);
await protectNoteHistory(note.note_id, dataKey, protect, sourceId);
}
async function protectNoteHistory(noteId, dataKey, protect) {
async function protectNoteHistory(noteId, dataKey, protect, sourceId) {
const historyToChange = await sql.getResults("SELECT * FROM notes_history WHERE note_id = ? AND is_protected != ?", [noteId, protect]);
for (const history of historyToChange) {
@ -132,7 +132,7 @@ async function protectNoteHistory(noteId, dataKey, protect) {
await sql.execute("UPDATE notes_history SET note_title = ?, note_text = ?, is_protected = ? WHERE note_history_id = ?",
[history.note_title, history.note_text, history.is_protected, history.note_history_id]);
await sync_table.addNoteHistorySync(history.note_history_id);
await sync_table.addNoteHistorySync(history.note_history_id, sourceId);
}
}
@ -174,7 +174,7 @@ async function updateNote(noteId, newNote, ctx) {
date_modified_to: nowStr
});
await sync_table.addNoteHistorySync(newNoteHistoryId);
await sync_table.addNoteHistorySync(newNoteHistoryId, ctx.sourceId);
}
await protectNoteHistory(noteId, ctx.getDataKeyOrNull(), newNote.detail.is_protected);
@ -186,15 +186,15 @@ async function updateNote(noteId, newNote, ctx) {
nowStr,
noteId]);
await sync_table.addNoteSync(noteId);
await sync_table.addNoteSync(noteId, ctx.sourceId);
});
}
async function deleteNote(noteTreeId) {
async function deleteNote(noteTreeId, sourceId) {
const now = utils.nowDate();
await sql.execute("UPDATE notes_tree SET is_deleted = 1, date_modified = ? WHERE note_tree_id = ?", [now, noteTreeId]);
await sync_table.addNoteTreeSync(noteTreeId);
await sync_table.addNoteTreeSync(noteTreeId, sourceId);
const noteId = await sql.getSingleValue("SELECT note_id FROM notes_tree WHERE note_tree_id = ?", [noteTreeId]);
@ -202,12 +202,12 @@ async function deleteNote(noteTreeId) {
if (!notDeletedNoteTreesCount) {
await sql.execute("UPDATE notes SET is_deleted = 1, date_modified = ? WHERE note_id = ?", [now, noteId]);
await sync_table.addNoteSync(noteId);
await sync_table.addNoteSync(noteId, sourceId);
const children = await sql.getResults("SELECT note_tree_id FROM notes_tree WHERE note_pid = ? AND is_deleted = 0", [noteId]);
for (const child of children) {
await deleteNote(child.note_tree_id);
await deleteNote(child.note_tree_id, sourceId);
}
}
}

View file

@ -16,9 +16,9 @@ async function getOption(optName) {
return row['opt_value'];
}
async function setOption(optName, optValue) {
async function setOption(optName, optValue, sourceId) {
if (SYNCED_OPTIONS.includes(optName)) {
await sync_table.addOptionsSync(optName);
await sync_table.addOptionsSync(optName, sourceId);
}
await sql.replace("options", {

View file

@ -1,5 +1,4 @@
const sql = require('./sql');
const source_id = require('./source_id');
const utils = require('./utils');
const messaging = require('./messaging');
const options = require('./options');
@ -9,24 +8,10 @@ let startTime = utils.nowDate();
let sentSyncId = [];
async function sendPing() {
const syncs = await sql.getResults("SELECT * FROM sync WHERE sync_date >= ? AND source_id != ?", [startTime, source_id.currentSourceId]);
const syncs = await sql.getResults("SELECT * FROM sync WHERE sync_date >= ?", [startTime]);
startTime = utils.nowDate();
const data = {};
const syncIds = [];
for (const sync of syncs) {
if (sentSyncId.includes(sync.id)) {
continue;
}
if (!data[sync.entity_name]) {
data[sync.entity_name] = [];
}
data[sync.entity_name].push(sync.entity_id);
syncIds.push(sync.id);
}
const syncData = syncs.filter(sync => !sentSyncId.includes(sync.id));
const lastSyncedPush = await options.getOption('last_synced_push');
@ -34,12 +19,12 @@ async function sendPing() {
messaging.sendMessage({
type: 'sync',
data: data,
data: syncData,
changesToPushCount: sync_setup.isSyncSetup ? changesToPushCount : 0
});
for (const syncId of syncIds) {
sentSyncId.push(syncId);
for (const sync of syncData) {
sentSyncId.push(sync.id);
}
}

View file

@ -11,7 +11,7 @@ function setDataKey(req, decryptedDataKey) {
}
function getProtectedSessionId(req) {
return req.headers['x-protected-session-id'];
return req.headers.protected_session_id;
}
function getDataKey(req) {

View file

@ -3,6 +3,8 @@
const protected_session = require('./protected_session');
module.exports = function(req) {
const sourceId = req.headers.source_id;
function isProtectedSessionAvailable() {
return protected_session.isProtectedSessionAvailable(req);
}
@ -24,6 +26,7 @@ module.exports = function(req) {
}
return {
sourceId,
isProtectedSessionAvailable,
getDataKey,
getDataKeyOrNull

View file

@ -2,31 +2,39 @@ const utils = require('./utils');
const log = require('./log');
const sql = require('./sql');
const currentSourceId = utils.randomString(12);
async function generateSourceId() {
const sourceId = utils.randomString(12);
log.info("Using sourceId=" + currentSourceId);
log.info("Generated sourceId=" + sourceId);
await sql.doInTransaction(async () => {
await sql.insert("source_ids", {
source_id: sourceId,
date_created: utils.nowDate()
});
});
await refreshSourceIds();
return sourceId;
}
async function refreshSourceIds() {
allSourceIds = await sql.getFlattenedResults("SELECT source_id FROM source_ids ORDER BY date_created DESC");
}
let allSourceIds = [];
sql.dbReady.then(async () => {
try {
await sql.doInTransaction(async () => {
await sql.insert("source_ids", {
source_id: currentSourceId,
date_created: utils.nowDate()
});
});
allSourceIds = await sql.getFlattenedResults("SELECT source_id FROM source_ids ORDER BY date_created DESC");
}
catch (e) {}
});
sql.dbReady.then(refreshSourceIds);
function isLocalSourceId(srcId) {
return allSourceIds.includes(srcId);
}
const currentSourceId = generateSourceId();
module.exports = {
generateSourceId,
currentSourceId,
isLocalSourceId
};

View file

@ -291,6 +291,10 @@
<script type="text/javascript">
const baseApiUrl = 'api/';
const glob = {
activeDialog: null,
sourceId: '<%= sourceId %>'
};
</script>
<!-- Required for correct loading of scripts in Electron -->