new mechanism to wait for sync after interaction with backend in Script API using api.waitForMaxKnownSyncId()

This commit is contained in:
zadam 2019-12-09 23:07:45 +01:00
parent 1e123f2390
commit 6f32d6fabe
7 changed files with 89 additions and 31 deletions

View File

@ -382,6 +382,11 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
* @param {function} handler * @param {function} handler
*/ */
this.bindGlobalShortcut = utils.bindGlobalShortcut; this.bindGlobalShortcut = utils.bindGlobalShortcut;
/**
* @method
*/
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
} }
export default FrontendScriptApi; export default FrontendScriptApi;

View File

@ -42,12 +42,16 @@ async function remove(url, headers = {}) {
let i = 1; let i = 1;
const reqResolves = {}; const reqResolves = {};
let maxKnownSyncId = 0;
async function call(method, url, data, headers = {}) { async function call(method, url, data, headers = {}) {
let resp;
if (utils.isElectron()) { if (utils.isElectron()) {
const ipc = require('electron').ipcRenderer; const ipc = require('electron').ipcRenderer;
const requestId = i++; const requestId = i++;
return new Promise((resolve, reject) => { resp = await new Promise((resolve, reject) => {
reqResolves[requestId] = resolve; reqResolves[requestId] = resolve;
if (REQUEST_LOGGING_ENABLED) { if (REQUEST_LOGGING_ENABLED) {
@ -64,32 +68,58 @@ async function call(method, url, data, headers = {}) {
}); });
} }
else { else {
return await ajax(url, method, data, headers); resp = await ajax(url, method, data, headers);
} }
const maxSyncIdStr = resp.headers['trilium-max-sync-id'];
if (maxSyncIdStr && maxSyncIdStr.trim()) {
maxKnownSyncId = Math.max(maxKnownSyncId, parseInt(maxSyncIdStr));
}
return resp.body;
} }
async function ajax(url, method, data, headers) { function ajax(url, method, data, headers) {
const options = { return new Promise((res, rej) => {
url: baseApiUrl + url, const options = {
type: method, url: baseApiUrl + url,
headers: getHeaders(headers), type: method,
timeout: 60000 headers: getHeaders(headers),
}; timeout: 60000,
success: (body, textStatus, jqXhr) => {
const respHeaders = {};
if (data) { jqXhr.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach(line => {
try { const parts = line.split(': ');
options.data = JSON.stringify(data); const header = parts.shift();
} respHeaders[header] = parts.join(': ');
catch (e) { });
console.log("Can't stringify data: ", data, " because of error: ", e)
}
options.contentType = "application/json";
}
return await $.ajax(options).catch(e => { res({
const message = "Error when calling " + method + " " + url + ": " + e.status + " - " + e.statusText; body,
toastService.showError(message); headers: respHeaders
toastService.throwError(message); });
},
error: (jqXhr, textStatus, error) => {
const message = "Error when calling " + method + " " + url + ": " + textStatus + " - " + error;
toastService.showError(message);
toastService.throwError(message);
rej(error);
}
};
if (data) {
try {
options.data = JSON.stringify(data);
} catch (e) {
console.log("Can't stringify data: ", data, " because of error: ", e)
}
options.contentType = "application/json";
}
$.ajax(options);
}); });
} }
@ -101,7 +131,10 @@ if (utils.isElectron()) {
console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode); console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode);
} }
reqResolves[arg.requestId](arg.body); reqResolves[arg.requestId]({
body: arg.body,
headers: arg.headers
});
delete reqResolves[arg.requestId]; delete reqResolves[arg.requestId];
}); });
@ -114,5 +147,6 @@ export default {
remove, remove,
ajax, ajax,
// don't remove, used from CKEditor image upload! // don't remove, used from CKEditor image upload!
getHeaders getHeaders,
getMaxKnownSyncId: () => maxKnownSyncId
}; };

View File

@ -77,7 +77,7 @@ async function stopWatch(what, func) {
} }
function formatValueWithWhitespace(val) { function formatValueWithWhitespace(val) {
return /[^\p{L}_-]/u.test(val) ? '"' + val + '"' : val; return /[^\w_-]/.test(val) ? '"' + val + '"' : val;
} }
function formatLabel(label) { function formatLabel(label) {

View File

@ -1,5 +1,6 @@
import utils from './utils.js'; import utils from './utils.js';
import toastService from "./toast.js"; import toastService from "./toast.js";
import server from "./server.js";
const $outstandingSyncsCount = $("#outstanding-syncs-count"); const $outstandingSyncsCount = $("#outstanding-syncs-count");
@ -71,8 +72,6 @@ async function handleMessage(event) {
// finish and set to null to signal somebody else can pick it up // finish and set to null to signal somebody else can pick it up
consumeQueuePromise = null; consumeQueuePromise = null;
} }
checkSyncIdListeners();
} }
else if (message.type === 'sync-hash-check-failed') { else if (message.type === 'sync-hash-check-failed') {
toastService.showError("Sync check failed!", 60000); toastService.showError("Sync check failed!", 60000);
@ -98,6 +97,10 @@ function waitForSyncId(desiredSyncId) {
}); });
} }
function waitForMaxKnownSyncId() {
return waitForSyncId(server.getMaxKnownSyncId());
}
function checkSyncIdListeners() { function checkSyncIdListeners() {
syncIdReachedListeners syncIdReachedListeners
.filter(l => l.desiredSyncId <= lastProcessedSyncId) .filter(l => l.desiredSyncId <= lastProcessedSyncId)
@ -129,6 +132,8 @@ async function consumeSyncData() {
lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncData[allSyncData.length - 1].id); lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncData[allSyncData.length - 1].id);
} }
checkSyncIdListeners();
} }
function connectWebSocket() { function connectWebSocket() {
@ -193,5 +198,6 @@ export default {
subscribeToMessages, subscribeToMessages,
subscribeToAllSyncMessages, subscribeToAllSyncMessages,
subscribeToOutsideSyncMessages, subscribeToOutsideSyncMessages,
waitForSyncId waitForSyncId,
waitForMaxKnownSyncId
}; };

View File

@ -12,10 +12,14 @@ function init(app) {
} }
}; };
const respHeaders = {};
const res = { const res = {
statusCode: 200, statusCode: 200,
getHeader: () => {}, getHeader: name => respHeaders[name],
setHeader: () => {}, setHeader: (name, value) => {
respHeaders[name] = value.toString();
},
status: statusCode => { status: statusCode => {
res.statusCode = statusCode; res.statusCode = statusCode;
return res; return res;
@ -24,6 +28,7 @@ function init(app) {
event.sender.send('server-response', { event.sender.send('server-response', {
requestId: arg.requestId, requestId: arg.requestId,
statusCode: res.statusCode, statusCode: res.statusCode,
headers: respHeaders,
body: obj body: obj
}); });
} }

View File

@ -44,6 +44,7 @@ const auth = require('../services/auth');
const cls = require('../services/cls'); const cls = require('../services/cls');
const sql = require('../services/sql'); const sql = require('../services/sql');
const protectedSessionService = require('../services/protected_session'); const protectedSessionService = require('../services/protected_session');
const syncTableService = require('../services/sync_table');
const csurf = require('csurf'); const csurf = require('csurf');
const csrfMiddleware = csurf({ const csrfMiddleware = csurf({
@ -52,6 +53,8 @@ const csrfMiddleware = csurf({
}); });
function apiResultHandler(req, res, result) { function apiResultHandler(req, res, result) {
res.setHeader('trilium-max-sync-id', syncTableService.getMaxSyncId());
// if it's an array and first element is integer then we consider this to be [statusCode, response] format // if it's an array and first element is integer then we consider this to be [statusCode, response] format
if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) { if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) {
const [statusCode, response] = result; const [statusCode, response] = result;

View File

@ -21,6 +21,10 @@ async function addEntitySync(entityName, entityId, sourceId) {
setTimeout(() => require('./ws').sendPingToAllClients(), 50); setTimeout(() => require('./ws').sendPingToAllClients(), 50);
} }
function getMaxSyncId() {
return syncs.length === 0 ? 0 : syncs[syncs.length - 1].id;
}
function getEntitySyncsNewerThan(syncId) { function getEntitySyncsNewerThan(syncId) {
return syncs.filter(s => s.id > syncId); return syncs.filter(s => s.id > syncId);
} }
@ -96,5 +100,6 @@ module.exports = {
addApiTokenSync: async (apiTokenId, sourceId) => await addEntitySync("api_tokens", apiTokenId, sourceId), addApiTokenSync: async (apiTokenId, sourceId) => await addEntitySync("api_tokens", apiTokenId, sourceId),
addEntitySync, addEntitySync,
fillAllSyncRows, fillAllSyncRows,
getEntitySyncsNewerThan getEntitySyncsNewerThan,
getMaxSyncId
}; };